instructor 1.2.4__tar.gz → 1.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {instructor-1.2.4 → instructor-1.2.6}/PKG-INFO +4 -3
- {instructor-1.2.4 → instructor-1.2.6}/instructor/__init__.py +1 -1
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/files.py +19 -15
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/hub.py +5 -5
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/jobs.py +38 -28
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/usage.py +17 -21
- {instructor-1.2.4 → instructor-1.2.6}/instructor/client.py +149 -65
- {instructor-1.2.4 → instructor-1.2.6}/instructor/client_anthropic.py +16 -15
- {instructor-1.2.4 → instructor-1.2.6}/instructor/client_cohere.py +14 -14
- {instructor-1.2.4 → instructor-1.2.6}/instructor/client_groq.py +6 -8
- {instructor-1.2.4 → instructor-1.2.6}/instructor/client_mistral.py +4 -6
- {instructor-1.2.4 → instructor-1.2.6}/instructor/distil.py +50 -28
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/citation.py +6 -6
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/iterable.py +12 -8
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/maybe.py +11 -12
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/parallel.py +5 -9
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/partial.py +14 -17
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/simple_type.py +6 -3
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/validators.py +1 -1
- {instructor-1.2.4 → instructor-1.2.6}/instructor/function_calls.py +52 -36
- {instructor-1.2.4 → instructor-1.2.6}/instructor/patch.py +13 -20
- {instructor-1.2.4 → instructor-1.2.6}/instructor/process_response.py +8 -13
- {instructor-1.2.4 → instructor-1.2.6}/instructor/retry.py +11 -11
- {instructor-1.2.4 → instructor-1.2.6}/instructor/utils.py +53 -11
- {instructor-1.2.4 → instructor-1.2.6}/pyproject.toml +25 -6
- {instructor-1.2.4 → instructor-1.2.6}/LICENSE +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/README.md +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/_types/__init__.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/_types/_alias.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/__init__.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/cli/cli.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/dsl/__init__.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/exceptions.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/mode.py +0 -0
- {instructor-1.2.4 → instructor-1.2.6}/instructor/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: instructor
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: structured outputs for llm
|
|
5
5
|
Home-page: https://github.com/jxnl/instructor
|
|
6
6
|
License: MIT
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Provides-Extra: anthropic
|
|
17
17
|
Provides-Extra: cohere
|
|
18
18
|
Provides-Extra: groq
|
|
19
|
+
Provides-Extra: litellm
|
|
19
20
|
Provides-Extra: mistralai
|
|
20
21
|
Provides-Extra: test-docs
|
|
21
22
|
Requires-Dist: aiohttp (>=3.9.1,<4.0.0)
|
|
@@ -25,11 +26,11 @@ Requires-Dist: diskcache (>=5.6.3,<6.0.0) ; extra == "test-docs"
|
|
|
25
26
|
Requires-Dist: docstring-parser (>=0.16,<0.17)
|
|
26
27
|
Requires-Dist: fastapi (>=0.109.2,<0.110.0) ; extra == "test-docs"
|
|
27
28
|
Requires-Dist: groq (>=0.4.2,<0.5.0) ; extra == "groq" or extra == "test-docs"
|
|
28
|
-
Requires-Dist: litellm (>=1.
|
|
29
|
+
Requires-Dist: litellm (>=1.35.31,<2.0.0) ; extra == "test-docs" or extra == "litellm"
|
|
29
30
|
Requires-Dist: mistralai (>=0.1.8,<0.2.0) ; extra == "test-docs" or extra == "mistralai"
|
|
30
31
|
Requires-Dist: openai (>=1.1.0,<2.0.0)
|
|
31
32
|
Requires-Dist: pandas (>=2.2.0,<3.0.0) ; extra == "test-docs"
|
|
32
|
-
Requires-Dist: pydantic (
|
|
33
|
+
Requires-Dist: pydantic (>=2.7.0,<3.0.0)
|
|
33
34
|
Requires-Dist: pydantic-core (>=2.18.0,<3.0.0)
|
|
34
35
|
Requires-Dist: pydantic_extra_types (>=2.6.0,<3.0.0) ; extra == "test-docs"
|
|
35
36
|
Requires-Dist: redis (>=5.0.1,<6.0.0) ; extra == "test-docs"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# type: ignore - stub mismatched
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
from datetime import datetime
|
|
3
|
-
from typing import
|
|
5
|
+
from typing import Literal, cast
|
|
4
6
|
|
|
5
7
|
import openai
|
|
6
8
|
import typer
|
|
@@ -14,7 +16,7 @@ console = Console()
|
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
# Sample response data
|
|
17
|
-
def generate_file_table(files:
|
|
19
|
+
def generate_file_table(files: list[openai.types.FileObject]) -> Table:
|
|
18
20
|
table = Table(
|
|
19
21
|
title="OpenAI Files",
|
|
20
22
|
)
|
|
@@ -36,7 +38,7 @@ def generate_file_table(files: List[openai.types.FileObject]) -> Table:
|
|
|
36
38
|
return table
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
def get_files() ->
|
|
41
|
+
def get_files() -> list[openai.types.FileObject]:
|
|
40
42
|
files = client.files.list()
|
|
41
43
|
files = files.data
|
|
42
44
|
files = sorted(files, key=lambda x: x.created_at, reverse=True)
|
|
@@ -50,15 +52,17 @@ def get_file_status(file_id: str) -> str:
|
|
|
50
52
|
|
|
51
53
|
@app.command(
|
|
52
54
|
help="Upload a file to OpenAI's servers, will monitor the upload status until it is processed",
|
|
53
|
-
)
|
|
55
|
+
)
|
|
54
56
|
def upload(
|
|
55
|
-
filepath: str = typer.Argument(
|
|
57
|
+
filepath: str = typer.Argument(help="Path to the file to upload"),
|
|
56
58
|
purpose: str = typer.Option("fine-tune", help="Purpose of the file"),
|
|
57
59
|
poll: int = typer.Option(5, help="Polling interval in seconds"),
|
|
58
60
|
) -> None:
|
|
61
|
+
# Literals aren't supported by Typer yet.
|
|
62
|
+
file_purpose = cast(Literal["fine-tune", "assistants"], purpose)
|
|
59
63
|
with open(filepath, "rb") as file:
|
|
60
|
-
response = client.files.create(file=file, purpose=
|
|
61
|
-
file_id = response["id"]
|
|
64
|
+
response = client.files.create(file=file, purpose=file_purpose)
|
|
65
|
+
file_id = response["id"] # type: ignore - types might be out of date
|
|
62
66
|
with console.status(f"Monitoring upload: {file_id}...") as status:
|
|
63
67
|
status.spinner_style = "dots"
|
|
64
68
|
while True:
|
|
@@ -71,10 +75,10 @@ def upload(
|
|
|
71
75
|
|
|
72
76
|
@app.command(
|
|
73
77
|
help="Download a file from OpenAI's servers",
|
|
74
|
-
)
|
|
78
|
+
)
|
|
75
79
|
def download(
|
|
76
|
-
file_id: str = typer.Argument(
|
|
77
|
-
output: str = typer.Argument(
|
|
80
|
+
file_id: str = typer.Argument(help="ID of the file to download"),
|
|
81
|
+
output: str = typer.Argument(help="Output path for the downloaded file"),
|
|
78
82
|
) -> None:
|
|
79
83
|
with console.status(f"[bold green]Downloading file {file_id}...", spinner="dots"):
|
|
80
84
|
content = client.files.download(file_id)
|
|
@@ -85,8 +89,8 @@ def download(
|
|
|
85
89
|
|
|
86
90
|
@app.command(
|
|
87
91
|
help="Delete a file from OpenAI's servers",
|
|
88
|
-
)
|
|
89
|
-
def delete(file_id: str = typer.Argument(
|
|
92
|
+
)
|
|
93
|
+
def delete(file_id: str = typer.Argument(help="ID of the file to delete")) -> None:
|
|
90
94
|
with console.status(f"[bold red]Deleting file {file_id}...", spinner="dots"):
|
|
91
95
|
try:
|
|
92
96
|
client.files.delete(file_id)
|
|
@@ -98,9 +102,9 @@ def delete(file_id: str = typer.Argument(..., help="ID of the file to delete"))
|
|
|
98
102
|
|
|
99
103
|
@app.command(
|
|
100
104
|
help="Monitor the status of a file on OpenAI's servers",
|
|
101
|
-
)
|
|
105
|
+
)
|
|
102
106
|
def status(
|
|
103
|
-
file_id: str = typer.Argument(
|
|
107
|
+
file_id: str = typer.Argument(help="ID of the file to check the status of"),
|
|
104
108
|
) -> None:
|
|
105
109
|
with console.status(f"Monitoring status of file {file_id}...") as status:
|
|
106
110
|
while True:
|
|
@@ -113,7 +117,7 @@ def status(
|
|
|
113
117
|
|
|
114
118
|
@app.command(
|
|
115
119
|
help="List the files on OpenAI's servers",
|
|
116
|
-
)
|
|
120
|
+
)
|
|
117
121
|
def list() -> None:
|
|
118
122
|
files = get_files()
|
|
119
123
|
console.log(generate_file_table(files))
|
|
@@ -58,7 +58,7 @@ class HubClient:
|
|
|
58
58
|
else:
|
|
59
59
|
raise Exception(f"Failed to fetch cookbooks: {response.status_code}")
|
|
60
60
|
|
|
61
|
-
def get_content_markdown(self, branch, slug):
|
|
61
|
+
def get_content_markdown(self, branch: str, slug: str) -> str:
|
|
62
62
|
"""Get markdown content."""
|
|
63
63
|
url = f"{self.base_url}/api/{branch}/items/{slug}/md/"
|
|
64
64
|
response = httpx.get(url)
|
|
@@ -67,7 +67,7 @@ class HubClient:
|
|
|
67
67
|
else:
|
|
68
68
|
raise Exception(f"Failed to fetch markdown content: {response.status_code}")
|
|
69
69
|
|
|
70
|
-
def get_content_python(self, branch, slug):
|
|
70
|
+
def get_content_python(self, branch: str, slug: str) -> str:
|
|
71
71
|
"""Get Python code blocks from content."""
|
|
72
72
|
url = f"{self.base_url}/api/{branch}/items/{slug}/py/"
|
|
73
73
|
response = httpx.get(url)
|
|
@@ -76,12 +76,12 @@ class HubClient:
|
|
|
76
76
|
else:
|
|
77
77
|
raise Exception(f"Failed to fetch Python content: {response.status_code}")
|
|
78
78
|
|
|
79
|
-
def get_cookbook_id(self, id: int, branch: str = "main") -> HubPage:
|
|
79
|
+
def get_cookbook_id(self, id: int, branch: str = "main") -> Optional[HubPage]:
|
|
80
80
|
for cookbook in self.get_cookbooks(branch):
|
|
81
81
|
if cookbook.id == id:
|
|
82
82
|
return cookbook
|
|
83
83
|
|
|
84
|
-
def get_cookbook_slug(self, slug: str, branch: str = "main") -> HubPage:
|
|
84
|
+
def get_cookbook_slug(self, slug: str, branch: str = "main") -> Optional[HubPage]:
|
|
85
85
|
for cookbook in self.get_cookbooks(branch):
|
|
86
86
|
if cookbook.slug == slug:
|
|
87
87
|
return cookbook
|
|
@@ -155,7 +155,7 @@ def pull(
|
|
|
155
155
|
|
|
156
156
|
if file:
|
|
157
157
|
with open(file, "w") as f:
|
|
158
|
-
f.write(output)
|
|
158
|
+
f.write(output) # type: ignore - markdown is writable
|
|
159
159
|
return
|
|
160
160
|
|
|
161
161
|
if page:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional, TypedDict
|
|
2
2
|
from openai import OpenAI
|
|
3
3
|
|
|
4
|
+
from openai.types.fine_tuning.job_create_params import Hyperparameters
|
|
4
5
|
import typer
|
|
5
6
|
import time
|
|
6
7
|
from rich.live import Live
|
|
7
8
|
from rich.table import Table
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from datetime import datetime
|
|
10
|
-
from typing import cast
|
|
11
11
|
from openai.types.fine_tuning import FineTuningJob
|
|
12
12
|
|
|
13
13
|
client = OpenAI()
|
|
@@ -15,10 +15,15 @@ app = typer.Typer()
|
|
|
15
15
|
console = Console()
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
class FuneTuningParams(TypedDict, total=False):
|
|
19
|
+
hyperparameters: Hyperparameters
|
|
20
|
+
validation_file: Optional[str]
|
|
21
|
+
suffix: Optional[str]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_table(jobs: list[FineTuningJob]) -> Table:
|
|
19
25
|
# Sorting the jobs by creation time
|
|
20
|
-
jobs = sorted(jobs, key=lambda x:
|
|
21
|
-
jobs = cast(List[FineTuningJob], jobs)
|
|
26
|
+
jobs = sorted(jobs, key=lambda x: x.created_at, reverse=True)
|
|
22
27
|
|
|
23
28
|
table = Table(
|
|
24
29
|
title="OpenAI Fine Tuning Job Monitoring",
|
|
@@ -66,7 +71,7 @@ def status_color(status: str) -> str:
|
|
|
66
71
|
)
|
|
67
72
|
|
|
68
73
|
|
|
69
|
-
def get_jobs(limit: int = 5) ->
|
|
74
|
+
def get_jobs(limit: int = 5) -> list[FineTuningJob]:
|
|
70
75
|
return client.fine_tuning.jobs.list(limit=limit).data
|
|
71
76
|
|
|
72
77
|
|
|
@@ -78,7 +83,7 @@ def get_file_status(file_id: str) -> str:
|
|
|
78
83
|
@app.command(
|
|
79
84
|
name="list",
|
|
80
85
|
help="Monitor the status of the most recent fine-tuning jobs.",
|
|
81
|
-
)
|
|
86
|
+
)
|
|
82
87
|
def watch(
|
|
83
88
|
limit: int = typer.Option(5, help="Limit the number of jobs to monitor"),
|
|
84
89
|
poll: int = typer.Option(5, help="Polling interval in seconds"),
|
|
@@ -97,24 +102,24 @@ def watch(
|
|
|
97
102
|
|
|
98
103
|
@app.command(
|
|
99
104
|
help="Create a fine-tuning job from an existing ID.",
|
|
100
|
-
)
|
|
105
|
+
)
|
|
101
106
|
def create_from_id(
|
|
102
|
-
id: str = typer.Argument(
|
|
107
|
+
id: str = typer.Argument(help="ID of the existing fine-tuning job"),
|
|
103
108
|
model: str = typer.Option("gpt-3.5-turbo", help="Model to use for fine-tuning"),
|
|
104
|
-
n_epochs: int = typer.Option(
|
|
109
|
+
n_epochs: Optional[int] = typer.Option(
|
|
105
110
|
None, help="Number of epochs for fine-tuning", show_default=False
|
|
106
111
|
),
|
|
107
|
-
batch_size: int = typer.Option(
|
|
112
|
+
batch_size: Optional[int] = typer.Option(
|
|
108
113
|
None, help="Batch size for fine-tuning", show_default=False
|
|
109
114
|
),
|
|
110
|
-
learning_rate_multiplier: float = typer.Option(
|
|
115
|
+
learning_rate_multiplier: Optional[float] = typer.Option(
|
|
111
116
|
None, help="Learning rate multiplier for fine-tuning", show_default=False
|
|
112
117
|
),
|
|
113
|
-
validation_file_id: str = typer.Option(
|
|
118
|
+
validation_file_id: Optional[str] = typer.Option(
|
|
114
119
|
None, help="ID of the uploaded validation file"
|
|
115
120
|
),
|
|
116
121
|
) -> None:
|
|
117
|
-
hyperparameters_dict:
|
|
122
|
+
hyperparameters_dict: Hyperparameters = {}
|
|
118
123
|
if n_epochs is not None:
|
|
119
124
|
hyperparameters_dict["n_epochs"] = n_epochs
|
|
120
125
|
if batch_size is not None:
|
|
@@ -128,7 +133,7 @@ def create_from_id(
|
|
|
128
133
|
job = client.fine_tuning.jobs.create(
|
|
129
134
|
training_file=id,
|
|
130
135
|
model=model,
|
|
131
|
-
hyperparameters=hyperparameters_dict
|
|
136
|
+
hyperparameters=hyperparameters_dict,
|
|
132
137
|
validation_file=validation_file_id if validation_file_id else None,
|
|
133
138
|
)
|
|
134
139
|
console.log(f"[bold green]Fine-tuning job created with ID: {job.id}")
|
|
@@ -137,24 +142,28 @@ def create_from_id(
|
|
|
137
142
|
|
|
138
143
|
@app.command(
|
|
139
144
|
help="Create a fine-tuning job from a file.",
|
|
140
|
-
)
|
|
145
|
+
)
|
|
141
146
|
def create_from_file(
|
|
142
|
-
file: str = typer.Argument(
|
|
147
|
+
file: str = typer.Argument(help="Path to the file for fine-tuning"),
|
|
143
148
|
model: str = typer.Option("gpt-3.5-turbo", help="Model to use for fine-tuning"),
|
|
144
149
|
poll: int = typer.Option(2, help="Polling interval in seconds"),
|
|
145
|
-
n_epochs: int = typer.Option(
|
|
150
|
+
n_epochs: Optional[int] = typer.Option(
|
|
146
151
|
None, help="Number of epochs for fine-tuning", show_default=False
|
|
147
152
|
),
|
|
148
|
-
batch_size: int = typer.Option(
|
|
153
|
+
batch_size: Optional[int] = typer.Option(
|
|
149
154
|
None, help="Batch size for fine-tuning", show_default=False
|
|
150
155
|
),
|
|
151
|
-
learning_rate_multiplier: float = typer.Option(
|
|
156
|
+
learning_rate_multiplier: Optional[float] = typer.Option(
|
|
152
157
|
None, help="Learning rate multiplier for fine-tuning", show_default=False
|
|
153
158
|
),
|
|
154
|
-
validation_file: str = typer.Option(
|
|
155
|
-
|
|
159
|
+
validation_file: Optional[str] = typer.Option(
|
|
160
|
+
None, help="Path to the validation file"
|
|
161
|
+
),
|
|
162
|
+
model_suffix: Optional[str] = typer.Option(
|
|
163
|
+
None, help="Suffix to identify the model"
|
|
164
|
+
),
|
|
156
165
|
) -> None:
|
|
157
|
-
hyperparameters_dict:
|
|
166
|
+
hyperparameters_dict: Hyperparameters = {}
|
|
158
167
|
if n_epochs is not None:
|
|
159
168
|
hyperparameters_dict["n_epochs"] = n_epochs
|
|
160
169
|
if batch_size is not None:
|
|
@@ -177,8 +186,9 @@ def create_from_file(
|
|
|
177
186
|
status.spinner_style = "dots"
|
|
178
187
|
while True:
|
|
179
188
|
file_status = get_file_status(file_id)
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
validation_file_status = (
|
|
190
|
+
get_file_status(validation_file_id) if validation_file_id else ""
|
|
191
|
+
)
|
|
182
192
|
|
|
183
193
|
if file_status == "processed" and (
|
|
184
194
|
not validation_file_id or validation_file_status == "processed"
|
|
@@ -192,7 +202,7 @@ def create_from_file(
|
|
|
192
202
|
|
|
193
203
|
time.sleep(poll)
|
|
194
204
|
|
|
195
|
-
additional_params:
|
|
205
|
+
additional_params: FuneTuningParams = {}
|
|
196
206
|
if hyperparameters_dict:
|
|
197
207
|
additional_params["hyperparameters"] = hyperparameters_dict
|
|
198
208
|
if validation_file:
|
|
@@ -218,9 +228,9 @@ def create_from_file(
|
|
|
218
228
|
|
|
219
229
|
@app.command(
|
|
220
230
|
help="Cancel a fine-tuning job.",
|
|
221
|
-
)
|
|
231
|
+
)
|
|
222
232
|
def cancel(
|
|
223
|
-
id: str = typer.Argument(
|
|
233
|
+
id: str = typer.Argument(help="ID of the fine-tuning job to cancel"),
|
|
224
234
|
) -> None:
|
|
225
235
|
with console.status(f"[bold red]Cancelling job {id}...", spinner="dots"):
|
|
226
236
|
try:
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any, Union
|
|
2
|
+
from collections.abc import Awaitable
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
import typer
|
|
4
5
|
import os
|
|
5
6
|
import aiohttp
|
|
6
7
|
import asyncio
|
|
8
|
+
from builtins import list as List
|
|
7
9
|
from collections import defaultdict
|
|
8
10
|
from rich.console import Console
|
|
9
11
|
from rich.table import Table
|
|
@@ -18,7 +20,7 @@ console = Console()
|
|
|
18
20
|
api_key = os.environ.get("OPENAI_API_KEY")
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
async def fetch_usage(date: str) ->
|
|
23
|
+
async def fetch_usage(date: str) -> dict[str, Any]:
|
|
22
24
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
23
25
|
url = f"https://api.openai.com/v1/usage?date={date}"
|
|
24
26
|
async with aiohttp.ClientSession() as session:
|
|
@@ -26,9 +28,9 @@ async def fetch_usage(date: str) -> Dict[str, Any]:
|
|
|
26
28
|
return await resp.json()
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
async def get_usage_for_past_n_days(n_days: int) ->
|
|
30
|
-
tasks = []
|
|
31
|
-
all_data = []
|
|
31
|
+
async def get_usage_for_past_n_days(n_days: int) -> list[dict[str, Any]]:
|
|
32
|
+
tasks: List[Awaitable[dict[str, Any]]] = [] # noqa: UP006 - conflicting with the fn name
|
|
33
|
+
all_data: List[dict[str, Any]] = [] # noqa: UP006 - conflicting with the fn name
|
|
32
34
|
with Progress() as progress:
|
|
33
35
|
if n_days > 1:
|
|
34
36
|
task = progress.add_task("[green]Fetching usage data...", total=n_days)
|
|
@@ -46,13 +48,7 @@ async def get_usage_for_past_n_days(n_days: int) -> List[Dict[str, Any]]:
|
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
# Define the cost per unit for each model
|
|
49
|
-
|
|
50
|
-
# from the first few items (?) in the dict, which may not be representative of
|
|
51
|
-
# the entire dict.
|
|
52
|
-
MODEL_COSTS: Dict[
|
|
53
|
-
ModelNames,
|
|
54
|
-
Union[Dict[str, float], float],
|
|
55
|
-
] = {
|
|
51
|
+
MODEL_COSTS = {
|
|
56
52
|
"gpt-4-0125-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
|
|
57
53
|
"gpt-4-turbo-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
|
|
58
54
|
"gpt-4-1106-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
|
|
@@ -79,7 +75,7 @@ MODEL_COSTS: Dict[
|
|
|
79
75
|
|
|
80
76
|
def get_model_cost(
|
|
81
77
|
model: ModelNames,
|
|
82
|
-
) -> Union[
|
|
78
|
+
) -> Union[dict[str, float], float]:
|
|
83
79
|
"""Get the cost details for a given model."""
|
|
84
80
|
if model in MODEL_COSTS:
|
|
85
81
|
return MODEL_COSTS[model]
|
|
@@ -104,7 +100,7 @@ def calculate_cost(
|
|
|
104
100
|
"""Calculate the cost based on the snapshot ID and number of tokens."""
|
|
105
101
|
cost = get_model_cost(snapshot_id)
|
|
106
102
|
|
|
107
|
-
if isinstance(cost, float):
|
|
103
|
+
if isinstance(cost, (float, int)):
|
|
108
104
|
return cost * (n_context_tokens + n_generated_tokens)
|
|
109
105
|
|
|
110
106
|
prompt_cost = cost["prompt"] * n_context_tokens
|
|
@@ -112,13 +108,13 @@ def calculate_cost(
|
|
|
112
108
|
return prompt_cost + completion_cost
|
|
113
109
|
|
|
114
110
|
|
|
115
|
-
def group_and_sum_by_date_and_snapshot(usage_data:
|
|
111
|
+
def group_and_sum_by_date_and_snapshot(usage_data: list[dict[str, Any]]) -> Table:
|
|
116
112
|
"""Group and sum the usage data by date and snapshot, including costs."""
|
|
117
|
-
summary:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
summary: defaultdict[str, defaultdict[str, dict[str, Union[int, float]]]] = (
|
|
114
|
+
defaultdict(
|
|
115
|
+
lambda: defaultdict(
|
|
116
|
+
lambda: {"total_requests": 0, "total_tokens": 0, "total_cost": 0.0}
|
|
117
|
+
)
|
|
122
118
|
)
|
|
123
119
|
)
|
|
124
120
|
|
|
@@ -160,7 +156,7 @@ def group_and_sum_by_date_and_snapshot(usage_data: List[Dict[str, Any]]) -> Tabl
|
|
|
160
156
|
return table
|
|
161
157
|
|
|
162
158
|
|
|
163
|
-
@app.command(help="Displays OpenAI API usage data for the past N days.")
|
|
159
|
+
@app.command(help="Displays OpenAI API usage data for the past N days.")
|
|
164
160
|
def list(
|
|
165
161
|
n: int = typer.Option(0, help="Number of days."),
|
|
166
162
|
) -> None:
|