Skip to content

Commit b25fd4c

Browse files
author
michal.zyga
committed
extend chat completion response
1 parent 10d72a7 commit b25fd4c

File tree

7 files changed

+132
-12
lines changed

7 files changed

+132
-12
lines changed

core/src/main/scala/sttp/openai/requests/completions/Usage.scala

+53-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,60 @@ package sttp.openai.requests.completions
22

33
import sttp.openai.json.SnakePickle
44

5-
case class Usage(promptTokens: Int, completionTokens: Int, totalTokens: Int)
5+
/** @param promptTokens
6+
* Number of tokens in the prompt.
7+
* @param completionTokens
8+
* Number of tokens in the generated completion.
9+
* @param totalTokens
10+
* Total number of tokens used in the request (prompt + completion).
11+
* @param completionTokensDetails
12+
* Breakdown of tokens used in a completion.
13+
* @param promptTokensDetails
14+
* Breakdown of tokens used in the prompt.
15+
*/
16+
case class Usage(
17+
promptTokens: Int,
18+
completionTokens: Int,
19+
totalTokens: Int,
20+
completionTokensDetails: CompletionTokensDetails,
21+
promptTokensDetails: PromptTokensDetails
22+
)
623

724
object Usage {
825
implicit val choicesR: SnakePickle.Reader[Usage] = SnakePickle.macroR[Usage]
926
}
27+
28+
/** @param acceptedPredictionTokens
29+
* When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.
30+
* @param audioTokens
31+
* Audio input tokens generated by the model.
32+
* @param reasoningTokens
33+
* Tokens generated by the model for reasoning.
34+
* @param rejectedPredictionTokens
35+
* When using Predicted Outputs, the number of tokens in the prediction that did not appear in the completion. However, like reasoning
36+
* tokens, these tokens are still counted in the total completion tokens for purposes of billing, output, and context window limits.
37+
*/
38+
case class CompletionTokensDetails(
39+
acceptedPredictionTokens: Int,
40+
audioTokens: Int,
41+
reasoningTokens: Int,
42+
rejectedPredictionTokens: Int
43+
)
44+
45+
object CompletionTokensDetails {
46+
implicit val completionTokensDetailsR: SnakePickle.Reader[CompletionTokensDetails] = SnakePickle.macroR[CompletionTokensDetails]
47+
}
48+
49+
/** @param audioTokens
50+
* Audio input tokens present in the prompt.
51+
* @param cachedTokens
52+
* Cached tokens present in the prompt.
53+
*/
54+
case class PromptTokensDetails(
55+
audioTokens: Int,
56+
cachedTokens: Int
57+
)
58+
59+
object PromptTokensDetails {
60+
implicit val promptTokensDetailsR: SnakePickle.Reader[PromptTokensDetails] = SnakePickle.macroR[PromptTokensDetails]
61+
}

core/src/main/scala/sttp/openai/requests/threads/runs/ThreadRunsResponseData.scala

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package sttp.openai.requests.threads.runs
22

33
import sttp.openai.json.SnakePickle
4-
import sttp.openai.requests.completions.Usage
54
import sttp.openai.requests.completions.chat.message.{Tool, ToolResources}
65

76
object ThreadRunsResponseData {
@@ -442,4 +441,17 @@ object ThreadRunsResponseData {
442441
object ListRunStepsResponse {
443442
implicit val listRunStepsResponseR: SnakePickle.Reader[ListRunStepsResponse] = SnakePickle.macroR[ListRunStepsResponse]
444443
}
444+
445+
/** @param promptTokens
446+
* Number of tokens in the prompt.
447+
* @param completionTokens
448+
* Number of tokens in the generated completion.
449+
* @param totalTokens
450+
* Total number of tokens used in the request (prompt + completion).
451+
*/
452+
case class Usage(promptTokens: Int, completionTokens: Int, totalTokens: Int)
453+
454+
object Usage {
455+
implicit val choicesR: SnakePickle.Reader[Usage] = SnakePickle.macroR[Usage]
456+
}
445457
}

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,17 @@ object ChatFixture {
151151
| "usage": {
152152
| "prompt_tokens": 10,
153153
| "completion_tokens": 10,
154-
| "total_tokens": 20
154+
| "total_tokens": 20,
155+
| "prompt_tokens_details": {
156+
| "cached_tokens": 1,
157+
| "audio_tokens": 2
158+
| },
159+
| "completion_tokens_details": {
160+
| "reasoning_tokens": 4,
161+
| "accepted_prediction_tokens": 3,
162+
| "rejected_prediction_tokens": 2,
163+
| "audio_tokens": 1
164+
| }
155165
| },
156166
| "choices": [
157167
| {

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

+22-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ object CompletionsFixture {
1717
| "usage": {
1818
| "prompt_tokens": 5,
1919
| "completion_tokens": 8,
20-
| "total_tokens": 13
20+
| "total_tokens": 13,
21+
| "prompt_tokens_details": {
22+
| "cached_tokens": 1,
23+
| "audio_tokens": 2
24+
| },
25+
| "completion_tokens_details": {
26+
| "reasoning_tokens": 4,
27+
| "accepted_prediction_tokens": 3,
28+
| "rejected_prediction_tokens": 2,
29+
| "audio_tokens": 1
30+
| }
2131
| }
2232
|}""".stripMargin
2333

@@ -43,7 +53,17 @@ object CompletionsFixture {
4353
| "usage":{
4454
| "prompt_tokens":11,
4555
| "completion_tokens":14,
46-
| "total_tokens":25
56+
| "total_tokens":25,
57+
| "prompt_tokens_details": {
58+
| "cached_tokens": 1,
59+
| "audio_tokens": 2
60+
| },
61+
| "completion_tokens_details": {
62+
| "reasoning_tokens": 4,
63+
| "accepted_prediction_tokens": 3,
64+
| "rejected_prediction_tokens": 2,
65+
| "audio_tokens": 1
66+
| }
4767
| }
4868
|}
4969
|

core/src/test/scala/sttp/openai/requests/completions/CompletionsDataSpec.scala

+16-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ class CompletionsDataSpec extends AnyFlatSpec with Matchers with EitherValues {
3131
usage = Usage(
3232
promptTokens = 5,
3333
completionTokens = 8,
34-
totalTokens = 13
34+
totalTokens = 13,
35+
completionTokensDetails = CompletionTokensDetails(
36+
acceptedPredictionTokens = 3,
37+
audioTokens = 1,
38+
reasoningTokens = 4,
39+
rejectedPredictionTokens = 2
40+
),
41+
promptTokensDetails = PromptTokensDetails(audioTokens = 2, cachedTokens = 1)
3542
)
3643
)
3744

@@ -97,7 +104,14 @@ class CompletionsDataSpec extends AnyFlatSpec with Matchers with EitherValues {
97104
usage = Usage(
98105
promptTokens = 11,
99106
completionTokens = 14,
100-
totalTokens = 25
107+
totalTokens = 25,
108+
completionTokensDetails = CompletionTokensDetails(
109+
acceptedPredictionTokens = 3,
110+
audioTokens = 1,
111+
reasoningTokens = 4,
112+
rejectedPredictionTokens = 2
113+
),
114+
promptTokensDetails = PromptTokensDetails(audioTokens = 2, cachedTokens = 1)
101115
)
102116
)
103117

core/src/test/scala/sttp/openai/requests/completions/chat/ChatDataSpec.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import org.scalatest.matchers.should.Matchers
66
import sttp.openai.fixtures
77
import sttp.openai.json.{SnakePickle, SttpUpickleApiExtension}
88
import sttp.openai.requests.completions.Stop.SingleStop
9-
import sttp.openai.requests.completions.Usage
109
import sttp.openai.requests.completions.chat.ChatRequestBody.Format.Mp3
1110
import sttp.openai.requests.completions.chat.ChatRequestBody.Voice.Ash
11+
import sttp.openai.requests.completions.{CompletionTokensDetails, PromptTokensDetails, Usage}
1212
import sttp.openai.utils.ChatCompletionFixtures._
1313

1414
class ChatDataSpec extends AnyFlatSpec with Matchers with EitherValues {
@@ -23,7 +23,14 @@ class ChatDataSpec extends AnyFlatSpec with Matchers with EitherValues {
2323
val usage: Usage = Usage(
2424
promptTokens = 10,
2525
completionTokens = 10,
26-
totalTokens = 20
26+
totalTokens = 20,
27+
completionTokensDetails = CompletionTokensDetails(
28+
acceptedPredictionTokens = 3,
29+
audioTokens = 1,
30+
reasoningTokens = 4,
31+
rejectedPredictionTokens = 2
32+
),
33+
promptTokensDetails = PromptTokensDetails(audioTokens = 2, cachedTokens = 1)
2734
)
2835

2936
val message: Message = Message(

core/src/test/scala/sttp/openai/requests/threads/runs/ThreadRunsDataSpec.scala

+8-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@ import org.scalatest.matchers.should.Matchers
66
import sttp.openai.fixtures
77
import sttp.openai.json.{SnakePickle, SttpUpickleApiExtension}
88
import sttp.openai.requests.completions.chat.message.Tool.{CodeInterpreterTool, FileSearchTool, FunctionTool}
9-
import sttp.openai.requests.completions.Usage
109
import sttp.openai.requests.completions.chat.message.ToolResource.CodeInterpreterToolResource
1110
import sttp.openai.requests.completions.chat.message.ToolResources
1211
import sttp.openai.requests.threads.ThreadsRequestBody.CreateThreadBody
1312
import sttp.openai.requests.threads.messages.ThreadMessagesRequestBody.CreateMessage
1413
import sttp.openai.requests.threads.runs.ThreadRunsRequestBody.ToolOutput
15-
import sttp.openai.requests.threads.runs.ThreadRunsResponseData.{ListRunStepsResponse, ListRunsResponse, MessageCreation, RunStepData}
14+
import sttp.openai.requests.threads.runs.ThreadRunsResponseData.{
15+
ListRunStepsResponse,
16+
ListRunsResponse,
17+
MessageCreation,
18+
RunStepData,
19+
Usage
20+
}
1621
import ujson.{Arr, Obj, Str}
1722

1823
class ThreadRunsDataSpec extends AnyFlatSpec with Matchers with EitherValues {
@@ -119,8 +124,8 @@ class ThreadRunsDataSpec extends AnyFlatSpec with Matchers with EitherValues {
119124
}
120125

121126
"Given list runs response as Json" should "be properly deserialized to case class" in {
122-
import sttp.openai.requests.threads.runs.ThreadRunsResponseData.RunData
123127
import sttp.openai.requests.threads.runs.ThreadRunsResponseData.ListRunsResponse._
128+
import sttp.openai.requests.threads.runs.ThreadRunsResponseData.RunData
124129

125130
// given
126131
val jsonResponse = fixtures.ThreadRunsFixture.jsonListRunsResponse

0 commit comments

Comments
 (0)