Skip to content

Commit 1ca9c62

Browse files
MarkDaoustcopybara-github
authored andcommitted
feat(interactions): populate output_text and output media helpers in the Java SDK
The `Interaction` model declared `outputText`, `outputImage`, `outputAudio` and `outputVideo` but the Java SDK never populated them. They are now derived from `steps` whenever an `Interaction` is constructed (including deserialization). The helper walks steps backwards from the end of the current turn: `user_input` ends the turn, `thought` steps and `model_output` steps without content are skipped, and tool call/result steps (or a non-text content item) end text collection once text has been found. Text parts are concatenated in order. The last image/audio/video produced by the model in the turn is exposed as `output_image`/`output_audio`/`output_video`. This matches the Python and TypeScript SDKs. PiperOrigin-RevId: 1005013079
1 parent 453c442 commit 1ca9c62

2 files changed

Lines changed: 397 additions & 0 deletions

File tree

‎src/main/java/com/google/genai/gaos/models/interactions/Interaction.java‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ public Interaction(
330330
this.updated = updated;
331331
this.usage = usage;
332332
this.webhookConfig = webhookConfig;
333+
populateOutputProperties();
333334
}
334335

335336
public Interaction(
@@ -581,6 +582,106 @@ public void setOutputs(List<Content> outputs) {
581582
.build();
582583
Step step = modelOutputStep;
583584
this.steps = java.util.Collections.singletonList(step);
585+
populateOutputProperties();
586+
}
587+
}
588+
589+
/**
590+
* Populates the SDK-only convenience fields ({@code output_text}, {@code output_image},
591+
* {@code output_audio}, {@code output_video}) from {@code steps}.
592+
*
593+
* <p>{@code output_text} is the concatenation of the trailing run of text content produced by
594+
* the model in the last turn. Thought steps and empty model outputs are skipped; a user input
595+
* step, a tool step after text was found, or non-text content after text was found ends the
596+
* run. {@code output_image}, {@code output_audio} and {@code output_video} are the last item of
597+
* each kind produced by the model since the last user input.
598+
*/
599+
private void populateOutputProperties() {
600+
if (this.steps == null || this.steps.isEmpty()) {
601+
return;
602+
}
603+
604+
List<String> textParts = new java.util.ArrayList<>();
605+
boolean collecting = false;
606+
outer:
607+
for (int i = this.steps.size() - 1; i >= 0; i--) {
608+
Step step = this.steps.get(i);
609+
if (step == null) {
610+
continue;
611+
}
612+
String stepType = step.type();
613+
if ("user_input".equals(stepType)) {
614+
break;
615+
}
616+
if ("thought".equals(stepType)) {
617+
continue;
618+
}
619+
if (!(step instanceof ModelOutputStep)) {
620+
if (collecting) {
621+
break;
622+
}
623+
continue;
624+
}
625+
List<Content> content = ((ModelOutputStep) step).content().orElse(null);
626+
if (content == null || content.isEmpty()) {
627+
continue;
628+
}
629+
for (int j = content.size() - 1; j >= 0; j--) {
630+
Content item = content.get(j);
631+
if (item instanceof TextContent) {
632+
collecting = true;
633+
textParts.add(((TextContent) item).text().orElse(""));
634+
} else if (collecting) {
635+
break outer;
636+
}
637+
}
638+
}
639+
640+
ImageContent image = null;
641+
AudioContent audio = null;
642+
VideoContent video = null;
643+
for (int i = this.steps.size() - 1; i >= 0; i--) {
644+
Step step = this.steps.get(i);
645+
if (step == null) {
646+
continue;
647+
}
648+
if ("user_input".equals(step.type())) {
649+
break;
650+
}
651+
if (!(step instanceof ModelOutputStep)) {
652+
continue;
653+
}
654+
List<Content> content = ((ModelOutputStep) step).content().orElse(null);
655+
if (content == null) {
656+
continue;
657+
}
658+
for (int j = content.size() - 1; j >= 0; j--) {
659+
Content item = content.get(j);
660+
if (image == null && item instanceof ImageContent) {
661+
image = (ImageContent) item;
662+
}
663+
if (audio == null && item instanceof AudioContent) {
664+
audio = (AudioContent) item;
665+
}
666+
if (video == null && item instanceof VideoContent) {
667+
video = (VideoContent) item;
668+
}
669+
}
670+
}
671+
672+
java.util.Collections.reverse(textParts);
673+
String text = String.join("", textParts);
674+
if (!text.isEmpty()) {
675+
this.outputText = text;
676+
}
677+
if (image != null) {
678+
this.outputImage = image;
679+
}
680+
if (audio != null) {
681+
this.outputAudio = audio;
682+
}
683+
if (video != null) {
684+
this.outputVideo = video;
584685
}
585686
}
586687

0 commit comments

Comments
 (0)