1+ import datetime
12import json
3+ import os
4+ import signal
25import sys
36import warnings
47
@@ -205,21 +208,34 @@ def list(cls, args):
205208 print (file )
206209
207210
208- class FineTuneCLI :
211+ class FineTune :
209212 @classmethod
210213 def list (cls , args ):
211214 resp = openai .FineTune .list ()
212215 print (resp )
213216
217+ @classmethod
218+ def _get_or_upload (cls , file ):
219+ try :
220+ openai .File .retrieve (file )
221+ except openai .error .InvalidRequestError as e :
222+ if e .http_status == 404 and os .path .isfile (file ):
223+ resp = openai .File .create (file = open (file ), purpose = "fine-tune" )
224+ sys .stdout .write (
225+ "Uploaded file from {file}: {id}\n " .format (file = file , id = resp ["id" ])
226+ )
227+ return resp ["id" ]
228+ return file
229+
214230 @classmethod
215231 def create (cls , args ):
216232 create_args = {
217- "train_file " : args .train_file ,
233+ "training_file " : cls . _get_or_upload ( args .training_file ) ,
218234 }
219- if args .test_file :
220- create_args ["test_file " ] = args .test_file
221- if args .base_model :
222- create_args ["base_model " ] = args .base_model
235+ if args .validation_file :
236+ create_args ["validation_file " ] = cls . _get_or_upload ( args .validation_file )
237+ if args .model :
238+ create_args ["model " ] = args .model
223239 if args .hparams :
224240 try :
225241 hparams = json .loads (args .hparams )
@@ -231,7 +247,35 @@ def create(cls, args):
231247 create_args .update (hparams )
232248
233249 resp = openai .FineTune .create (** create_args )
234- print (resp )
250+
251+ if args .no_wait :
252+ print (resp )
253+ return
254+
255+ sys .stdout .write (
256+ "Created job: {job_id}\n "
257+ "Streaming events until the job is complete...\n \n "
258+ "(Ctrl-C will interrupt the stream, but not cancel the job)\n " .format (
259+ job_id = resp ["id" ]
260+ )
261+ )
262+ cls ._stream_events (resp ["id" ])
263+
264+ resp = openai .FineTune .retrieve (id = resp ["id" ])
265+ status = resp ["status" ]
266+ sys .stdout .write ("\n Job complete! Status: {status}" .format (status = status ))
267+ if status == "succeeded" :
268+ sys .stdout .write (" 🎉" )
269+ sys .stdout .write (
270+ "\n Try out your fine-tuned model: {model}\n "
271+ "(Pass this as the model parameter to a completion request)" .format (
272+ model = resp ["fine_tuned_model" ]
273+ )
274+ )
275+ # TODO(rachel): Print instructions on how to use the model here.
276+ elif status == "failed" :
277+ sys .stdout .write ("\n Please contact support@openai.com for assistance." )
278+ sys .stdout .write ("\n " )
235279
236280 @classmethod
237281 def get (cls , args ):
@@ -240,8 +284,39 @@ def get(cls, args):
240284
241285 @classmethod
242286 def events (cls , args ):
243- resp = openai .FineTune .list_events (id = args .id )
244- print (resp )
287+ if not args .stream :
288+ resp = openai .FineTune .list_events (id = args .id )
289+ print (resp )
290+ return
291+ cls ._stream_events (args .id )
292+
293+ @classmethod
294+ def _stream_events (cls , job_id ):
295+ def signal_handler (sig , frame ):
296+ status = openai .FineTune .retrieve (job_id ).status
297+ sys .stdout .write (
298+ "\n Stream interrupted. Job is still {status}. "
299+ "To cancel your job, run:\n "
300+ "`openai api fine_tunes.cancel -i {job_id}`\n " .format (
301+ status = status , job_id = job_id
302+ )
303+ )
304+ sys .exit (0 )
305+
306+ signal .signal (signal .SIGINT , signal_handler )
307+
308+ events = openai .FineTune .stream_events (job_id )
309+ # TODO(rachel): Add a nifty spinner here.
310+ for event in events :
311+ sys .stdout .write (
312+ "[%s] %s"
313+ % (
314+ datetime .datetime .fromtimestamp (event ["created_at" ]),
315+ event ["message" ],
316+ )
317+ )
318+ sys .stdout .write ("\n " )
319+ sys .stdout .flush ()
245320
246321 @classmethod
247322 def cancel (cls , args ):
@@ -436,27 +511,52 @@ def help(args):
436511
437512 # Finetune
438513 sub = subparsers .add_parser ("fine_tunes.list" )
439- sub .set_defaults (func = FineTuneCLI .list )
514+ sub .set_defaults (func = FineTune .list )
440515
441516 sub = subparsers .add_parser ("fine_tunes.create" )
442- sub .add_argument ("-t" , "--train_file" , required = True , help = "File to train" )
443- sub .add_argument ("--test_file" , help = "File to test" )
444517 sub .add_argument (
445- "-b" ,
446- "--base_model" ,
447- help = "The model name to start the run from" ,
518+ "-t" ,
519+ "--training_file" ,
520+ required = True ,
521+ help = "JSONL file containing prompt-completion examples for training. This can "
522+ "be the ID of a file uploaded through the OpenAI API (e.g. file-abcde12345) "
523+ "or a local file path." ,
524+ )
525+ sub .add_argument (
526+ "-v" ,
527+ "--validation_file" ,
528+ help = "JSONL file containing prompt-completion examples for validation. This can "
529+ "be the ID of a file uploaded through the OpenAI API (e.g. file-abcde12345) "
530+ "or a local file path." ,
531+ )
532+ sub .add_argument (
533+ "-m" ,
534+ "--model" ,
535+ help = "The model to start fine-tuning from" ,
536+ )
537+ sub .add_argument (
538+ "--no_wait" ,
539+ action = "store_true" ,
540+ help = "If set, returns immediately after creating the job. Otherwise, waits for the job to complete." ,
448541 )
449542 sub .add_argument ("-p" , "--hparams" , help = "Hyperparameter JSON" )
450- sub .set_defaults (func = FineTuneCLI .create )
543+ sub .set_defaults (func = FineTune .create )
451544
452545 sub = subparsers .add_parser ("fine_tunes.get" )
453546 sub .add_argument ("-i" , "--id" , required = True , help = "The id of the fine-tune job" )
454- sub .set_defaults (func = FineTuneCLI .get )
547+ sub .set_defaults (func = FineTune .get )
455548
456549 sub = subparsers .add_parser ("fine_tunes.events" )
457550 sub .add_argument ("-i" , "--id" , required = True , help = "The id of the fine-tune job" )
458- sub .set_defaults (func = FineTuneCLI .events )
551+ sub .add_argument (
552+ "-s" ,
553+ "--stream" ,
554+ action = "store_true" ,
555+ help = "If set, events will be streamed until the job is done. Otherwise, "
556+ "displays the event history to date." ,
557+ )
558+ sub .set_defaults (func = FineTune .events )
459559
460560 sub = subparsers .add_parser ("fine_tunes.cancel" )
461561 sub .add_argument ("-i" , "--id" , required = True , help = "The id of the fine-tune job" )
462- sub .set_defaults (func = FineTuneCLI .cancel )
562+ sub .set_defaults (func = FineTune .cancel )
0 commit comments