Skip to content

Commit 85ef34f

Browse files
committed
Responses API:
- Introduced new parameters in CreateModelResponseSettings: prompt, promptCacheKey, background, maxToolCalls, safetyIdentifier, serviceTier, streamOptions, and topLogprobs. - Added Prompt and StreamOptions case classes to support new features. - Updated JSON format handling for CreateModelResponseSettings to accommodate the new fields. - Enhanced unit tests to validate the new settings and their serialization/deserialization.
1 parent c7890a3 commit 85ef34f

File tree

6 files changed

+369
-40
lines changed

6 files changed

+369
-40
lines changed

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/CreateModelResponseSettings.scala

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@ import io.cequence.openaiscala.domain.responsesapi.tools.Tool
1111
* @param include
1212
* Specify additional output data to include in the model response. Currently supported
1313
* values are:
14+
* - web_search_call.action.sources: Include the sources of the web search tool call.
15+
* - code_interpreter_call.outputs: Includes the outputs of python code execution in code
16+
* interpreter tool call items.
17+
* - computer_call_output.output.image_url: Include image urls from the computer call output.
1418
* - file_search_call.results: Include the search results of the file search tool call.
1519
* - message.input_image.image_url: Include image urls from the input message.
16-
* - computer_call_output.output.image_url: Include image urls from the computer call output.
20+
* - message.output_text.logprobs: Include logprobs with assistant messages.
21+
* - reasoning.encrypted_content: Includes an encrypted version of reasoning tokens in
22+
* reasoning item outputs. This enables reasoning items to be used in multi-turn
23+
* conversations when using the Responses API statelessly (like when the store parameter is
24+
* set to false, or when an organization is enrolled in the zero data retention program).
1725
* @param instructions
1826
* Inserts a system (or developer) message as the first item in the model's context.
1927
* @param maxOutputTokens
@@ -52,6 +60,36 @@ import io.cequence.openaiscala.domain.responsesapi.tools.Tool
5260
* @param user
5361
* A unique identifier representing your end-user, which can help OpenAI to monitor and
5462
* detect abuse. Learn more.
63+
* @param prompt
64+
* Reference to a prompt template and its variables.
65+
* @param promptCacheKey
66+
* Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.
67+
* Replaces the user field.
68+
* @param background
69+
* Whether to run the model response in the background. Optional, defaults to false.
70+
* @param maxToolCalls
71+
* The maximum number of total calls to built-in tools that can be processed in a response.
72+
* This maximum number applies across all built-in tool calls, not per individual tool. Any
73+
* further attempts to call a tool by the model will be ignored. Optional.
74+
* @param safetyIdentifier
75+
* A stable identifier used to help detect users of your application that may be violating
76+
* OpenAI's usage policies. The IDs should be a string that uniquely identifies each user. We
77+
* recommend hashing their username or email address, in order to avoid sending us any
78+
* identifying information Optional.
79+
* @param serviceTier
80+
* Specifies the processing type used for serving the request.
81+
* - If set to 'auto', then the request will be processed with the service tier configured in
82+
* the Project settings. Unless otherwise configured, the Project will use 'default'.
83+
* - If set to 'default', then the request will be processed with the standard pricing and
84+
* performance for the selected model.
85+
* - If set to 'flex' or 'priority', then the request will be processed with the
86+
* corresponding service tier.
87+
* - When not set, the default behavior is 'auto'.
88+
* @param streamOptions
89+
* Options for streaming responses.
90+
* @param topLogprobs
91+
* An integer between 0 and 20 specifying the number of most likely tokens to return at each
92+
* token position, each with an associated log probability. Optional
5593
*/
5694
final case class CreateModelResponseSettings(
5795
model: String,
@@ -70,5 +108,112 @@ final case class CreateModelResponseSettings(
70108
tools: Seq[Tool] = Nil,
71109
topP: Option[Double] = None,
72110
truncation: Option[TruncationStrategy] = None,
73-
user: Option[String] = None
111+
user: Option[String] = None,
112+
prompt: Option[Prompt] = None,
113+
promptCacheKey: Option[String] = None,
114+
background: Option[Boolean] = None,
115+
maxToolCalls: Option[Int] = None,
116+
safetyIdentifier: Option[String] = None,
117+
serviceTier: Option[String] = None,
118+
streamOptions: Option[StreamOptions] = None,
119+
topLogprobs: Option[Int] = None
120+
)
121+
122+
object CreateModelResponseSettings {
123+
124+
def toAuxPart1(x: CreateModelResponseSettings) =
125+
CreateModelResponseSettingsAuxPart1(
126+
model = x.model,
127+
include = x.include,
128+
instructions = x.instructions,
129+
maxOutputTokens = x.maxOutputTokens,
130+
metadata = x.metadata,
131+
parallelToolCalls = x.parallelToolCalls,
132+
previousResponseId = x.previousResponseId,
133+
reasoning = x.reasoning,
134+
store = x.store,
135+
stream = x.stream,
136+
temperature = x.temperature,
137+
text = x.text
138+
)
139+
140+
def toAuxPart2(x: CreateModelResponseSettings) =
141+
CreateModelResponseSettingsAuxPart2(
142+
toolChoice = x.toolChoice,
143+
tools = x.tools,
144+
topP = x.topP,
145+
truncation = x.truncation,
146+
user = x.user,
147+
prompt = x.prompt,
148+
promptCacheKey = x.promptCacheKey,
149+
background = x.background,
150+
maxToolCalls = x.maxToolCalls,
151+
safetyIdentifier = x.safetyIdentifier,
152+
serviceTier = x.serviceTier,
153+
streamOptions = x.streamOptions,
154+
topLogprobs = x.topLogprobs
155+
)
156+
157+
private def fromParts(
158+
part1: CreateModelResponseSettingsAuxPart1,
159+
part2: CreateModelResponseSettingsAuxPart2
160+
) =
161+
CreateModelResponseSettings(
162+
model = part1.model,
163+
include = part1.include,
164+
instructions = part1.instructions,
165+
maxOutputTokens = part1.maxOutputTokens,
166+
metadata = part1.metadata,
167+
parallelToolCalls = part1.parallelToolCalls,
168+
previousResponseId = part1.previousResponseId,
169+
reasoning = part1.reasoning,
170+
store = part1.store,
171+
stream = part1.stream,
172+
temperature = part1.temperature,
173+
text = part1.text,
174+
toolChoice = part2.toolChoice,
175+
tools = part2.tools,
176+
topP = part2.topP,
177+
truncation = part2.truncation,
178+
user = part2.user,
179+
prompt = part2.prompt,
180+
promptCacheKey = part2.promptCacheKey,
181+
background = part2.background,
182+
maxToolCalls = part2.maxToolCalls,
183+
safetyIdentifier = part2.safetyIdentifier,
184+
serviceTier = part2.serviceTier,
185+
streamOptions = part2.streamOptions,
186+
topLogprobs = part2.topLogprobs
187+
)
188+
}
189+
190+
final case class CreateModelResponseSettingsAuxPart1(
191+
model: String,
192+
include: Seq[String],
193+
instructions: Option[String],
194+
maxOutputTokens: Option[Int],
195+
metadata: Option[Map[String, String]],
196+
parallelToolCalls: Option[Boolean],
197+
previousResponseId: Option[String],
198+
reasoning: Option[ReasoningConfig],
199+
store: Option[Boolean],
200+
stream: Option[Boolean],
201+
temperature: Option[Double],
202+
text: Option[TextResponseConfig]
203+
)
204+
205+
final case class CreateModelResponseSettingsAuxPart2(
206+
toolChoice: Option[ToolChoice],
207+
tools: Seq[Tool],
208+
topP: Option[Double],
209+
truncation: Option[TruncationStrategy],
210+
user: Option[String],
211+
prompt: Option[Prompt],
212+
promptCacheKey: Option[String],
213+
background: Option[Boolean],
214+
maxToolCalls: Option[Int],
215+
safetyIdentifier: Option[String],
216+
serviceTier: Option[String],
217+
streamOptions: Option[StreamOptions],
218+
topLogprobs: Option[Int]
74219
)

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/JsonFormats.scala

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,18 @@ object JsonFormats {
313313
}
314314
}
315315

316+
private def writesNonEmpty(fieldName: String) = (jsObject: JsObject) => {
317+
val include = (jsObject \ fieldName).as[JsArray].value
318+
if (include.nonEmpty) jsObject else jsObject.-(fieldName)
319+
}
320+
321+
implicit lazy val promptFormat: OFormat[Prompt] = Json.format[Prompt]
322+
323+
implicit lazy val streamOptionsFormat: OFormat[StreamOptions] = Json.format[StreamOptions]
324+
316325
// create model response
317-
implicit lazy val createModelResponseSettingsReads: Reads[CreateModelResponseSettings] =
326+
private implicit lazy val createModelResponseSettingsAuxPart1Reads
327+
: Reads[CreateModelResponseSettingsAuxPart1] =
318328
(
319329
(__ \ "model").read[String] and
320330
(__ \ "include").readWithDefault[Seq[String]](Nil) and
@@ -327,20 +337,11 @@ object JsonFormats {
327337
(__ \ "store").readNullable[Boolean] and
328338
(__ \ "stream").readNullable[Boolean] and
329339
(__ \ "temperature").readNullable[Double] and
330-
(__ \ "text").readNullable[TextResponseConfig] and
331-
(__ \ "tool_choice").readNullable[ToolChoice] and
332-
(__ \ "tools").readWithDefault[Seq[Tool]](Nil) and
333-
(__ \ "top_p").readNullable[Double] and
334-
(__ \ "truncation").readNullable[TruncationStrategy] and
335-
(__ \ "user").readNullable[String]
336-
)(CreateModelResponseSettings.apply _)
337-
338-
private def writesNonEmpty(fieldName: String) = (jsObject: JsObject) => {
339-
val include = (jsObject \ fieldName).as[JsArray].value
340-
if (include.nonEmpty) jsObject else jsObject.-(fieldName)
341-
}
340+
(__ \ "text").readNullable[TextResponseConfig]
341+
)(CreateModelResponseSettingsAuxPart1.apply _)
342342

343-
implicit lazy val createModelResponseSettingsWrites: OWrites[CreateModelResponseSettings] =
343+
private implicit lazy val createModelResponseSettingsAuxPart1Writes
344+
: OWrites[CreateModelResponseSettingsAuxPart1] =
344345
(
345346
(__ \ "model").write[String] and
346347
(__ \ "include").write[Seq[String]].transform(writesNonEmpty("include")) and
@@ -353,34 +354,89 @@ object JsonFormats {
353354
(__ \ "store").writeNullable[Boolean] and
354355
(__ \ "stream").writeNullable[Boolean] and
355356
(__ \ "temperature").writeNullable[Double] and
356-
(__ \ "text").writeNullable[TextResponseConfig] and
357-
(__ \ "tool_choice").writeNullable[ToolChoice] and
357+
(__ \ "text").writeNullable[TextResponseConfig]
358+
)(unlift(CreateModelResponseSettingsAuxPart1.unapply))
359+
360+
private implicit lazy val createModelResponseSettingsAuxPart2Reads
361+
: Reads[CreateModelResponseSettingsAuxPart2] =
362+
(
363+
(__ \ "tool_choice").readNullable[ToolChoice] and
364+
(__ \ "tools").readWithDefault[Seq[Tool]](Nil) and
365+
(__ \ "top_p").readNullable[Double] and
366+
(__ \ "truncation").readNullable[TruncationStrategy] and
367+
(__ \ "user").readNullable[String] and
368+
(__ \ "prompt").readNullable[Prompt] and
369+
(__ \ "prompt_cache_key").readNullable[String] and
370+
(__ \ "background").readNullable[Boolean] and
371+
(__ \ "max_tool_calls").readNullable[Int] and
372+
(__ \ "safety_identifier").readNullable[String] and
373+
(__ \ "service_tier").readNullable[String] and
374+
(__ \ "stream_options").readNullable[StreamOptions] and
375+
(__ \ "top_logprobs").readNullable[Int]
376+
)(CreateModelResponseSettingsAuxPart2.apply _)
377+
378+
private implicit lazy val createModelResponseSettingsAuxPart2Writes
379+
: OWrites[CreateModelResponseSettingsAuxPart2] =
380+
(
381+
(__ \ "tool_choice").writeNullable[ToolChoice] and
358382
(__ \ "tools").write[Seq[Tool]].transform(writesNonEmpty("tools")) and
359383
(__ \ "top_p").writeNullable[Double] and
360384
(__ \ "truncation").writeNullable[TruncationStrategy] and
361-
(__ \ "user").writeNullable[String]
362-
)((x: CreateModelResponseSettings) =>
363-
(
364-
x.model,
365-
x.include,
366-
x.instructions,
367-
x.maxOutputTokens,
368-
x.metadata,
369-
x.parallelToolCalls,
370-
x.previousResponseId,
371-
x.reasoning,
372-
x.store,
373-
x.stream,
374-
x.temperature,
375-
x.text,
376-
x.toolChoice,
377-
x.tools,
378-
x.topP,
379-
x.truncation,
380-
x.user
381-
)
385+
(__ \ "user").writeNullable[String] and
386+
(__ \ "prompt").writeNullable[Prompt] and
387+
(__ \ "prompt_cache_key").writeNullable[String] and
388+
(__ \ "background").writeNullable[Boolean] and
389+
(__ \ "max_tool_calls").writeNullable[Int] and
390+
(__ \ "safety_identifier").writeNullable[String] and
391+
(__ \ "service_tier").writeNullable[String] and
392+
(__ \ "stream_options").writeNullable[StreamOptions] and
393+
(__ \ "top_logprobs").writeNullable[Int]
394+
)(unlift(CreateModelResponseSettingsAuxPart2.unapply))
395+
396+
// Compose Reads and Writes for CreateModelResponseSettings using the AuxPart1 and AuxPart2
397+
implicit lazy val createModelResponseSettingsReads: Reads[CreateModelResponseSettings] =
398+
for {
399+
part1 <- createModelResponseSettingsAuxPart1Reads
400+
part2 <- createModelResponseSettingsAuxPart2Reads
401+
} yield CreateModelResponseSettings(
402+
model = part1.model,
403+
include = part1.include,
404+
instructions = part1.instructions,
405+
maxOutputTokens = part1.maxOutputTokens,
406+
metadata = part1.metadata,
407+
parallelToolCalls = part1.parallelToolCalls,
408+
previousResponseId = part1.previousResponseId,
409+
reasoning = part1.reasoning,
410+
store = part1.store,
411+
stream = part1.stream,
412+
temperature = part1.temperature,
413+
text = part1.text,
414+
toolChoice = part2.toolChoice,
415+
tools = part2.tools,
416+
topP = part2.topP,
417+
truncation = part2.truncation,
418+
user = part2.user,
419+
prompt = part2.prompt,
420+
promptCacheKey = part2.promptCacheKey,
421+
background = part2.background,
422+
maxToolCalls = part2.maxToolCalls,
423+
safetyIdentifier = part2.safetyIdentifier,
424+
serviceTier = part2.serviceTier,
425+
streamOptions = part2.streamOptions,
426+
topLogprobs = part2.topLogprobs
382427
)
383428

429+
implicit lazy val createModelResponseSettingsWrites: OWrites[CreateModelResponseSettings] =
430+
OWrites[CreateModelResponseSettings] { x =>
431+
val part1Json = createModelResponseSettingsAuxPart1Writes.writes(
432+
CreateModelResponseSettings.toAuxPart1(x)
433+
)
434+
val part2Json = createModelResponseSettingsAuxPart2Writes.writes(
435+
CreateModelResponseSettings.toAuxPart2(x)
436+
)
437+
part1Json ++ part2Json
438+
}
439+
384440
implicit lazy val createModelResponseSettingsFormat: OFormat[CreateModelResponseSettings] =
385441
OFormat(createModelResponseSettingsReads, createModelResponseSettingsWrites)
386442

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.cequence.openaiscala.domain.responsesapi
2+
3+
/**
4+
* Reusable prompts are intended to be used with Response API.
5+
*
6+
* Here's how it works:
7+
* - Create a reusable prompt in the dashboard with placeholders like {{customer_name}}.
8+
* - Use the prompt in your API request with the prompt parameter. The prompt parameter
9+
* object has three properties you can configure: id — Unique identifier of your prompt,
10+
* found in the dashboard version — A specific version of your prompt (defaults to the
11+
* "current" version as specified in the dashboard) variables — A map of values to
12+
* substitute in for variables in your prompt. The substitution values can either be
13+
* strings, or other Response input message types like input_image or input_file. See the
14+
* full API reference.
15+
*
16+
* @param id
17+
* The unique identifier of the prompt template to use.
18+
* @param variables
19+
* Optional map of values to substitute in for variables in your prompt. The substitution
20+
* values can either be strings, or other Response input types like images or files.
21+
* @param version
22+
* Optional version of the prompt template.
23+
*/
24+
case class Prompt(
25+
id: String,
26+
variables: Map[String, String],
27+
version: Option[String] = None
28+
)

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/ReasoningConfig.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ import io.cequence.wsclient.domain.EnumValue
1212
* @param generateSummary
1313
* A summary of the reasoning performed by the model. This can be useful for debugging and
1414
* understanding the model's reasoning process. One of "concise" or "detailed". Optional.
15+
* @param summary
16+
* A summary of the reasoning performed by the model. This can be useful for debugging and
17+
* understanding the model's reasoning process. One of auto, concise, or detailed. Optional.
1518
*/
1619
case class ReasoningConfig(
1720
effort: Option[ReasoningEffort] = None,
18-
generateSummary: Option[String] = None
21+
@deprecated("Use summary instead", "1.3.0")
22+
generateSummary: Option[String] = None,
23+
summary: Option[String] = None
1924
)
2025

2126
/**
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.cequence.openaiscala.domain.responsesapi
2+
3+
/**
4+
* Options for streaming responses.
5+
*
6+
* @param includeObfuscation
7+
* When true, stream obfuscation will be enabled. Stream obfuscation adds random characters
8+
* to an obfuscation field on streaming delta events to normalize payload sizes as a
9+
* mitigation to certain side-channel attacks. These obfuscation fields are included by
10+
* default, but add a small amount of overhead to the data stream. You can set
11+
* include_obfuscation to false to optimize for bandwidth if you trust the network links
12+
* between your application and the OpenAI API.
13+
*/
14+
case class StreamOptions(
15+
includeObfuscation: Option[Boolean] = None
16+
)

0 commit comments

Comments
 (0)