Skip to content

Commit 157a868

Browse files
author
michal.zyga
committed
add list fine tuning job events method
1 parent 67c192f commit 157a868

File tree

5 files changed

+160
-4
lines changed

5 files changed

+160
-4
lines changed

core/src/main/scala/sttp/openai/OpenAI.scala

+23-3
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ class OpenAI(authToken: String, baseUri: Uri = OpenAIUris.OpenAIBaseUri) {
554554
*/
555555
def createFineTuningJob(fineTuningRequestBody: FineTuningJobRequestBody): Request[Either[OpenAIException, FineTuningJobResponse]] =
556556
openAIAuthRequest
557-
.post(openAIUris.FineTuning)
557+
.post(openAIUris.FineTuningJobs)
558558
.body(fineTuningRequestBody)
559559
.response(asJson_parseErrors[FineTuningJobResponse])
560560

@@ -565,14 +565,31 @@ class OpenAI(authToken: String, baseUri: Uri = OpenAIUris.OpenAIBaseUri) {
565565
def listFineTuningJobs(
566566
queryParameters: finetuning.QueryParameters = finetuning.QueryParameters.empty
567567
): Request[Either[OpenAIException, ListFineTuningJobResponse]] = {
568-
val uri = openAIUris.FineTuning
568+
val uri = openAIUris.FineTuningJobs
569569
.withParams(queryParameters.toMap)
570570

571571
openAIAuthRequest
572572
.get(uri)
573573
.response(asJson_parseErrors[ListFineTuningJobResponse])
574574
}
575575

576+
/** Get status updates for a fine-tuning job.
577+
*
578+
* [[https://platform.openai.com/docs/api-reference/fine-tuning/list-events]]
579+
*/
580+
def listFineTuningJobEvents(
581+
fineTuningJobId: String,
582+
queryParameters: finetuning.QueryParameters = finetuning.QueryParameters.empty
583+
): Request[Either[OpenAIException, ListFineTuningJobEventResponse]] = {
584+
val uri = openAIUris
585+
.fineTuningJobEvents(fineTuningJobId)
586+
.withParams(queryParameters.toMap)
587+
588+
openAIAuthRequest
589+
.get(uri)
590+
.response(asJson_parseErrors[ListFineTuningJobEventResponse])
591+
}
592+
576593
/** Gets info about the fine-tune job.
577594
*
578595
* [[https://platform.openai.com/docs/api-reference/embeddings/create]]
@@ -1068,7 +1085,7 @@ private class OpenAIUris(val baseUri: Uri) {
10681085
val Files: Uri = uri"$baseUri/files"
10691086
val Models: Uri = uri"$baseUri/models"
10701087
val Moderations: Uri = uri"$baseUri/moderations"
1071-
val FineTuning: Uri = uri"$baseUri/fine_tuning/jobs"
1088+
val FineTuningJobs: Uri = uri"$baseUri/fine_tuning/jobs"
10721089
val Transcriptions: Uri = audioBase.addPath("transcriptions")
10731090
val Translations: Uri = audioBase.addPath("translations")
10741091
val VariationsImage: Uri = imageBase.addPath("variations")
@@ -1078,6 +1095,9 @@ private class OpenAIUris(val baseUri: Uri) {
10781095
val ThreadsRuns: Uri = uri"$baseUri/threads/runs"
10791096
val VectorStores: Uri = uri"$baseUri/vector_stores"
10801097

1098+
def fineTuningJob(fineTuningJobId: String): Uri = FineTuningJobs.addPath(fineTuningJobId)
1099+
def fineTuningJobEvents(fineTuningJobId: String): Uri = fineTuningJob(fineTuningJobId).addPath("events")
1100+
10811101
def file(fileId: String): Uri = Files.addPath(fileId)
10821102
def fileContent(fileId: String): Uri = Files.addPath(fileId, "content")
10831103
def model(modelId: String): Uri = Models.addPath(modelId)

core/src/main/scala/sttp/openai/OpenAISyncClient.scala

+16-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ import sttp.openai.requests.embeddings.EmbeddingsRequestBody.EmbeddingsBody
1717
import sttp.openai.requests.embeddings.EmbeddingsResponseBody.EmbeddingResponse
1818
import sttp.openai.requests.files.FilesResponseData.{DeletedFileData, FileData, FilesResponse}
1919
import sttp.openai.requests.finetuning
20-
import sttp.openai.requests.finetuning.{FineTuningJobRequestBody, FineTuningJobResponse, ListFineTuningJobResponse}
20+
import sttp.openai.requests.finetuning.{
21+
FineTuningJobRequestBody,
22+
FineTuningJobResponse,
23+
ListFineTuningJobEventResponse,
24+
ListFineTuningJobResponse
25+
}
2126
import sttp.openai.requests.images.ImageResponseData.ImageResponse
2227
import sttp.openai.requests.images.creation.ImageCreationRequestBody.ImageCreationBody
2328
import sttp.openai.requests.images.edit.ImageEditsConfig
@@ -369,6 +374,16 @@ class OpenAISyncClient private (
369374
def listFineTuningJobs(queryParameters: finetuning.QueryParameters = finetuning.QueryParameters.empty): ListFineTuningJobResponse =
370375
sendOrThrow(openAI.listFineTuningJobs(queryParameters))
371376

377+
/** Get status updates for a fine-tuning job.
378+
*
379+
* [[https://platform.openai.com/docs/api-reference/fine-tuning/list-events]]
380+
*/
381+
def listFineTuningJobEvents(
382+
fineTuningJobId: String,
383+
queryParameters: finetuning.QueryParameters = finetuning.QueryParameters.empty
384+
): ListFineTuningJobEventResponse =
385+
sendOrThrow(openAI.listFineTuningJobEvents(fineTuningJobId, queryParameters))
386+
372387
/** Gets info about the fine-tune job.
373388
*
374389
* [[https://platform.openai.com/docs/api-reference/embeddings/create]]

core/src/main/scala/sttp/openai/requests/finetuning/FineTuningJobResponse.scala

+40
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,43 @@ case class ListFineTuningJobResponse(
128128
object ListFineTuningJobResponse {
129129
implicit val listFineTuningResponseR: SnakePickle.Reader[ListFineTuningJobResponse] = SnakePickle.macroR[ListFineTuningJobResponse]
130130
}
131+
132+
/** @param `object`
133+
* The object type, which is always "fine_tuning.job.event".
134+
* @param id
135+
* The object identifier.
136+
* @param createdAt
137+
* The Unix timestamp (in seconds) for when the fine-tuning job was created.
138+
* @param level
139+
* The log level of the event.
140+
* @param message
141+
* The message of the event.
142+
* @param `type`
143+
* The type of event.
144+
* @param data
145+
* The data associated with the event.
146+
*/
147+
case class FineTuningJobEventResponse(
148+
`object`: String,
149+
id: String,
150+
createdAt: Int,
151+
level: String,
152+
message: String,
153+
`type`: String,
154+
data: Map[String, ujson.Value]
155+
)
156+
157+
object FineTuningJobEventResponse {
158+
implicit val fineTuningJobEventResponseR: SnakePickle.Reader[FineTuningJobEventResponse] = SnakePickle.macroR[FineTuningJobEventResponse]
159+
}
160+
161+
case class ListFineTuningJobEventResponse(
162+
`object`: String = "list",
163+
data: Seq[FineTuningJobEventResponse],
164+
hasMore: Boolean
165+
)
166+
167+
object ListFineTuningJobEventResponse {
168+
implicit val listFineTuningJobEventResponseR: SnakePickle.Reader[ListFineTuningJobEventResponse] =
169+
SnakePickle.macroR[ListFineTuningJobEventResponse]
170+
}

core/src/test/scala/sttp/openai/fixtures/FineTuningJobFixture.scala

+37
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,43 @@ object FineTuningJobFixture {
112112
| "hasMore": false
113113
|}""".stripMargin
114114

115+
val jsonListFineTuningJobEventsResponse: String = """{
116+
| "object": "list",
117+
| "data": [
118+
| {
119+
| "object": "fine_tuning.job.event",
120+
| "id": "ft-event-ddTJfwuMVpfLXseO0Am0Gqjm",
121+
| "created_at": 1721764800,
122+
| "level": "info",
123+
| "message": "Fine tuning job successfully completed",
124+
| "data": null,
125+
| "type": "message"
126+
| },
127+
| {
128+
| "object": "fine_tuning.job.event",
129+
| "id": "ft-event-tyiGuB72evQncpH87xe505Sv",
130+
| "created_at": 1721764800,
131+
| "level": "info",
132+
| "message": "New fine-tuned model created: ft:gpt-4o-mini:openai::7p4lURel",
133+
| "data": {},
134+
| "type": "message"
135+
| },
136+
| {
137+
| "object": "fine_tuning.job.event",
138+
| "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F",
139+
| "created_at": 1721764800,
140+
| "level": "error",
141+
| "message": "Fine-tuning job failed.",
142+
| "data": {
143+
| "job_id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F",
144+
| "error": "Insufficient training data."
145+
| },
146+
| "type": "message"
147+
| }
148+
| ],
149+
| "has_more": true
150+
|}""".stripMargin
151+
115152
val fineTuningJobResponse: FineTuningJobResponse = FineTuningJobResponse(
116153
id = "ft-id",
117154
createdAt = 1000,

core/src/test/scala/sttp/openai/requests/finetuning/FineTuningDataSpec.scala

+44
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.scalatest.matchers.should.Matchers
66
import sttp.openai.fixtures.FineTuningJobFixture
77
import sttp.openai.json.{SnakePickle, SttpUpickleApiExtension}
88
import sttp.openai.requests.finetuning.FineTuningModel.GPT35Turbo0125
9+
import ujson.Str
910

1011
class FineTuningDataSpec extends AnyFlatSpec with Matchers with EitherValues {
1112

@@ -92,4 +93,47 @@ class FineTuningDataSpec extends AnyFlatSpec with Matchers with EitherValues {
9293
deserializedJsonResponse.value shouldBe expectedResponse
9394
}
9495

96+
"Given list fine tuning job events response as Json" should "be properly deserialized to case class" in {
97+
// given
98+
val jsonResponse = FineTuningJobFixture.jsonListFineTuningJobEventsResponse
99+
val expectedResponse: ListFineTuningJobEventResponse = ListFineTuningJobEventResponse(
100+
data = Seq(
101+
FineTuningJobEventResponse(
102+
`object` = "fine_tuning.job.event",
103+
id = "ft-event-ddTJfwuMVpfLXseO0Am0Gqjm",
104+
createdAt = 1721764800,
105+
level = "info",
106+
message = "Fine tuning job successfully completed",
107+
data = null,
108+
`type` = "message"
109+
),
110+
FineTuningJobEventResponse(
111+
`object` = "fine_tuning.job.event",
112+
id = "ft-event-tyiGuB72evQncpH87xe505Sv",
113+
createdAt = 1721764800,
114+
level = "info",
115+
message = "New fine-tuned model created: ft:gpt-4o-mini:openai::7p4lURel",
116+
data = Map(),
117+
`type` = "message"
118+
),
119+
FineTuningJobEventResponse(
120+
`object` = "fine_tuning.job.event",
121+
id = "ft-AF1WoRqd3aJAHsqc9NY7iL8F",
122+
createdAt = 1721764800,
123+
level = "error",
124+
message = "Fine-tuning job failed.",
125+
data = Map("job_id" -> Str("ft-AF1WoRqd3aJAHsqc9NY7iL8F"), "error" -> Str("Insufficient training data.")),
126+
`type` = "message"
127+
)
128+
),
129+
hasMore = true
130+
)
131+
// when
132+
val deserializedJsonResponse: Either[Exception, ListFineTuningJobEventResponse] =
133+
SttpUpickleApiExtension.deserializeJsonSnake[ListFineTuningJobEventResponse].apply(jsonResponse)
134+
135+
// then
136+
deserializedJsonResponse.value shouldBe expectedResponse
137+
}
138+
95139
}

0 commit comments

Comments
 (0)