Skip to content

Commit edf5a4a

Browse files
author
michal.zyga
committed
extend chat completion request body
1 parent a2a41d9 commit edf5a4a

File tree

3 files changed

+221
-6
lines changed

3 files changed

+221
-6
lines changed

core/src/main/scala/sttp/openai/requests/completions/chat/ChatRequestBody.scala

+175-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,46 @@ object ChatRequestBody {
191191
* Controls which (if any) function is called by the model.
192192
* @param user
193193
* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
194+
* @param store
195+
* Whether or not to store the output of this chat completion request for use in our model distillation or evals products.
196+
* @param reasoningEffort
197+
* Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort
198+
* can result in faster responses and fewer tokens used on reasoning in a response.
199+
* @param metadata
200+
* Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object
201+
* in a structured format, and querying for objects via API or the dashboard. Keys are strings with a maximum length of 64 characters.
202+
* Values are strings with a maximum length of 512 characters.
203+
* @param logprobs
204+
* Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token
205+
* returned in the content of message.
206+
* @param topLogprobs
207+
* An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated
208+
* log probability. logprobs must be set to true if this parameter is used.
209+
* @param maxCompletionTokens
210+
* An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning
211+
* tokens.
212+
* @param modalities
213+
* Output types that you would like the model to generate for this request. Most models are capable of generating text, which is the
214+
* default: ["text"]. The gpt-4o-audio-preview model can also be used to generate audio. To request that this model generate both text
215+
* and audio responses, you can use: ["text", "audio"].
216+
* @param serviceTier
217+
* Specifies the latency tier to use for processing the request. This parameter is relevant for customers subscribed to the scale tier
218+
* service:
219+
* - If set to 'auto', and the Project is Scale tier enabled, the system will utilize scale tier credits until they are exhausted.
220+
* - If set to 'auto', and the Project is not Scale tier enabled, the request will be processed using the default service tier with a
221+
* lower uptime SLA and no latency guarantee.
222+
* - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency
223+
* guarantee.
224+
* - When not set, the default behavior is 'auto'.
225+
* @param parallelToolCalls
226+
* Whether to enable parallel function calling during tool use.
227+
* @param streamOptions
228+
* Options for streaming response. Only set this when you set stream: true.
229+
* @param prediction
230+
* Configuration for a Predicted Output, which can greatly improve response times when large parts of the model response are known
231+
* ahead of time. This is most common when you are regenerating a file with only minor changes to most of the content.
232+
* @param audio
233+
* Parameters for audio output. Required when audio output is requested with modalities: ["audio"].
194234
*/
195235
case class ChatBody(
196236
messages: Seq[Message],
@@ -207,7 +247,19 @@ object ChatRequestBody {
207247
topP: Option[Double] = None,
208248
tools: Option[Seq[Tool]] = None,
209249
toolChoice: Option[ToolChoice] = None,
210-
user: Option[String] = None
250+
user: Option[String] = None,
251+
store: Option[Boolean] = None,
252+
reasoningEffort: Option[ReasoningEffort] = None,
253+
metadata: Option[Map[String, String]] = None,
254+
logprobs: Option[Boolean] = None,
255+
topLogprobs: Option[Int] = None,
256+
maxCompletionTokens: Option[Int] = None,
257+
modalities: Option[Seq[String]] = None,
258+
serviceTier: Option[String] = None,
259+
parallelToolCalls: Option[Boolean] = None,
260+
streamOptions: Option[StreamOptions] = None,
261+
prediction: Option[Prediction] = None,
262+
audio: Option[Audio] = None
211263
)
212264

213265
object ChatBody {
@@ -220,6 +272,128 @@ object ChatRequestBody {
220272
implicit val chatRequestW: SnakePickle.Writer[ChatBody] = SnakePickle.macroW[ChatBody]
221273
}
222274

275+
/** @param voice
276+
* The voice the model uses to respond. Supported voices are ash, ballad, coral, sage, and verse (also supported but not recommended
277+
* are alloy, echo, and shimmer; these voices are less expressive).
278+
* @param format
279+
* Specifies the output audio format. Must be one of wav, mp3, flac, opus, or pcm16.
280+
*/
281+
case class Audio(
282+
voice: Voice,
283+
format: Format
284+
)
285+
286+
object Audio {
287+
implicit val audioW: SnakePickle.Writer[Audio] = SnakePickle.macroW[Audio]
288+
}
289+
290+
sealed abstract class Voice(val value: String)
291+
292+
object Voice {
293+
case object Ash extends Voice("ash")
294+
case object Ballad extends Voice("ballad")
295+
case object Coral extends Voice("coral")
296+
case object Sage extends Voice("sage")
297+
case object Verse extends Voice("verse")
298+
case object Alloy extends Voice("alloy")
299+
case object Echo extends Voice("echo")
300+
case object Shimmer extends Voice("shimmer")
301+
case class CustomVoice(customVoice: String) extends Voice(customVoice)
302+
303+
implicit val voiceW: SnakePickle.Writer[Voice] = SnakePickle
304+
.writer[ujson.Value]
305+
.comap[Voice](voice => SnakePickle.writeJs(voice.value))
306+
}
307+
308+
sealed abstract class Format(val value: String)
309+
310+
object Format {
311+
case object Wav extends Format("wav")
312+
case object Mp3 extends Format("mp3")
313+
case object Flac extends Format("flac")
314+
case object Opus extends Format("opus")
315+
case object Pcm16 extends Format("pcm16")
316+
case class CustomFormat(customFormat: String) extends Format(customFormat)
317+
318+
implicit val formatW: SnakePickle.Writer[Format] = SnakePickle
319+
.writer[ujson.Value]
320+
.comap[Format](format => SnakePickle.writeJs(format.value))
321+
}
322+
323+
/** @param `type`
324+
* The type of the predicted content you want to provide. This type is currently always content.
325+
* @param content
326+
* The content that should be matched when generating a model response. If generated tokens would match this content, the entire model
327+
* response can be returned much more quickly.
328+
*/
329+
case class Prediction(
330+
`type`: String,
331+
content: Content
332+
)
333+
334+
object Prediction {
335+
implicit val predictionW: SnakePickle.Writer[Prediction] = SnakePickle.macroW[Prediction]
336+
}
337+
338+
sealed trait Content
339+
case class SingleContent(value: String) extends Content
340+
case class MultipartContent(value: Seq[ContentPart]) extends Content
341+
342+
object Content {
343+
implicit val contentW: SnakePickle.Writer[Content] = SnakePickle
344+
.writer[ujson.Value]
345+
.comap[Content] {
346+
case SingleContent(value) => SnakePickle.writeJs(value)
347+
case MultipartContent(value) => SnakePickle.writeJs(value)
348+
}
349+
}
350+
351+
/** An array of content parts with a defined type. Supported options differ based on the model being used to generate the response. Can
352+
* contain text inputs.
353+
*
354+
* @param `type`
355+
* The type of the content part.
356+
* @param text
357+
* The text content.
358+
*/
359+
case class ContentPart(
360+
`type`: String,
361+
text: String
362+
)
363+
364+
object ContentPart {
365+
implicit val contentPartW: SnakePickle.Writer[ContentPart] = SnakePickle.macroW[ContentPart]
366+
}
367+
368+
/** @param includeUsage
369+
* If set, an additional chunk will be streamed before the data: [DONE] message. The usage field on this chunk shows the token usage
370+
* statistics for the entire request, and the choices field will always be an empty array. All other chunks will also include a usage
371+
* field, but with a null value.
372+
*/
373+
case class StreamOptions(includeUsage: Option[Boolean] = None)
374+
375+
object StreamOptions {
376+
implicit val streamOptionsW: SnakePickle.Writer[StreamOptions] = SnakePickle.macroW[StreamOptions]
377+
}
378+
379+
sealed abstract class ReasoningEffort(val value: String)
380+
381+
object ReasoningEffort {
382+
383+
implicit val reasoningEffortW: SnakePickle.Writer[ReasoningEffort] = SnakePickle
384+
.writer[ujson.Value]
385+
.comap[ReasoningEffort](reasoningEffort => SnakePickle.writeJs(reasoningEffort.value))
386+
387+
case object Low extends ReasoningEffort("low")
388+
389+
case object Medium extends ReasoningEffort("medium")
390+
391+
case object High extends ReasoningEffort("high")
392+
393+
case class CustomReasoningEffort(customReasoningEffort: String) extends ReasoningEffort(customReasoningEffort)
394+
395+
}
396+
223397
sealed abstract class ChatCompletionModel(val value: String)
224398

225399
object ChatCompletionModel {

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,34 @@ object ChatFixture {
111111
| "name": "function"
112112
| }
113113
| },
114-
| "user": "testUser"
114+
| "user": "testUser",
115+
| "store": true,
116+
| "reasoning_effort": "low",
117+
| "metadata": {
118+
| "key": "value"
119+
| },
120+
| "logprobs": true,
121+
| "top_logprobs": 1,
122+
| "max_completion_tokens": 10,
123+
| "modalities": ["text", "audio"],
124+
| "service_tier": "advanced",
125+
| "parallel_tool_calls": true,
126+
| "stream_options": {
127+
| "include_usage": true
128+
| },
129+
| "prediction": {
130+
| "type": "content",
131+
| "content": [
132+
| {
133+
| "type": "code",
134+
| "text": "simple text"
135+
| }
136+
| ]
137+
| },
138+
| "audio": {
139+
| "voice": "ash",
140+
| "format": "mp3"
141+
| }
115142
|}""".stripMargin
116143

117144
val jsonResponse: String =

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ import org.scalatest.EitherValues
44
import org.scalatest.flatspec.AnyFlatSpec
55
import org.scalatest.matchers.should.Matchers
66
import sttp.openai.fixtures
7-
import sttp.openai.json.SnakePickle
8-
import sttp.openai.json.SttpUpickleApiExtension
7+
import sttp.openai.json.{SnakePickle, SttpUpickleApiExtension}
98
import sttp.openai.requests.completions.Stop.SingleStop
109
import sttp.openai.requests.completions.Usage
10+
import sttp.openai.requests.completions.chat.ChatRequestBody.Format.Mp3
11+
import sttp.openai.requests.completions.chat.ChatRequestBody.Voice.Ash
1112
import sttp.openai.utils.ChatCompletionFixtures._
1213

1314
class ChatDataSpec extends AnyFlatSpec with Matchers with EitherValues {
1415

1516
"Given chat completions response as Json" should "be properly deserialized to case class" in {
16-
import ChatRequestResponseData._
1717
import ChatRequestResponseData.ChatResponse._
18+
import ChatRequestResponseData._
1819

1920
// given
2021
val jsonResponse = fixtures.ChatFixture.jsonResponse
@@ -72,7 +73,20 @@ class ChatDataSpec extends AnyFlatSpec with Matchers with EitherValues {
7273
responseFormat = Some(ResponseFormat.JsonObject),
7374
toolChoice = Some(ToolChoice.ToolFunction("function")),
7475
stop = Some(SingleStop("\n")),
75-
user = Some("testUser")
76+
user = Some("testUser"),
77+
store = Some(true),
78+
reasoningEffort = Some(ReasoningEffort.Low),
79+
metadata = Some(Map("key" -> "value")),
80+
logprobs = Some(true),
81+
topLogprobs = Some(1),
82+
maxCompletionTokens = Some(10),
83+
modalities = Some(Seq("text", "audio")),
84+
serviceTier = Some("advanced"),
85+
parallelToolCalls = Some(true),
86+
streamOptions = Some(StreamOptions(includeUsage = Some(true))),
87+
prediction =
88+
Some(Prediction(`type` = "content", content = MultipartContent(value = Seq(ContentPart(`type` = "code", text = "simple text"))))),
89+
audio = Some(Audio(voice = Ash, format = Mp3))
7690
)
7791

7892
val jsonRequest: ujson.Value = ujson.read(fixtures.ChatFixture.jsonRequest)

0 commit comments

Comments
 (0)