Skip to content

Commit 8df8dda

Browse files
committed
add Google GenAI adapter
1 parent c689a9e commit 8df8dda

File tree

5 files changed

+194
-4
lines changed

5 files changed

+194
-4
lines changed

chat-agent-model/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>com.javaaidev.llmagentspec</groupId>
88
<artifactId>llm-agent-spec</artifactId>
9-
<version>0.5.0</version>
9+
<version>0.6.0</version>
1010
</parent>
1111

1212
<artifactId>chat-agent-model</artifactId>

google-genai-adapter/pom.xml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.javaaidev.llmagentspec</groupId>
8+
<artifactId>llm-agent-spec</artifactId>
9+
<version>0.6.0</version>
10+
</parent>
11+
12+
<artifactId>google-genai-adapter</artifactId>
13+
<name>Google GenAI Adapter</name>
14+
15+
<properties>
16+
<java.version>17</java.version>
17+
<maven.compiler.source>${java.version}</maven.compiler.source>
18+
<maven.compiler.target>${java.version}</maven.compiler.target>
19+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
20+
<google-genai.version>1.24.0</google-genai.version>
21+
<spring.version>6.2.14</spring.version>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>com.javaaidev.llmagentspec</groupId>
27+
<artifactId>chat-agent-model</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>com.google.genai</groupId>
32+
<artifactId>google-genai</artifactId>
33+
<version>${google-genai.version}</version>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.springframework</groupId>
37+
<artifactId>spring-web</artifactId>
38+
<version>${spring.version}</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.reactivex.rxjava3</groupId>
42+
<artifactId>rxjava</artifactId>
43+
<version>3.1.12</version>
44+
</dependency>
45+
</dependencies>
46+
</project>
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.javaaidev.chatagent.googlegenai;
2+
3+
import com.google.genai.types.Content;
4+
import com.google.genai.types.Part;
5+
import com.javaaidev.chatagent.model.Attachment;
6+
import com.javaaidev.chatagent.model.ChatAgentRequest;
7+
import com.javaaidev.chatagent.model.ChatAgentResponse;
8+
import com.javaaidev.chatagent.model.FileMessagePart;
9+
import com.javaaidev.chatagent.model.ImageMessagePart;
10+
import com.javaaidev.chatagent.model.ReasoningMessagePart;
11+
import com.javaaidev.chatagent.model.TextMessagePart;
12+
import com.javaaidev.chatagent.model.ThreadAssistantMessage;
13+
import com.javaaidev.chatagent.model.ThreadAssistantMessagePart;
14+
import com.javaaidev.chatagent.model.ThreadUserMessage;
15+
import com.javaaidev.chatagent.model.ThreadUserMessagePart;
16+
import io.reactivex.rxjava3.core.Flowable;
17+
import java.nio.charset.StandardCharsets;
18+
import java.util.ArrayList;
19+
import java.util.Base64;
20+
import org.springframework.http.codec.ServerSentEvent;
21+
22+
/**
23+
* Convert models between chat agent and Google GenAI
24+
*/
25+
public class ModelAdapter {
26+
27+
private ModelAdapter() {
28+
}
29+
30+
/**
31+
* Convert a {@linkplain ChatAgentRequest} to a {@linkplain Content}
32+
*
33+
* @param request {@linkplain ChatAgentRequest} of chat agent
34+
* @return {@linkplain Content}
35+
*/
36+
public static Content fromRequest(ChatAgentRequest request) {
37+
var parts = new ArrayList<Part>();
38+
for (var message : request.messages()) {
39+
if (message instanceof ThreadUserMessage userMessage) {
40+
var textBuilder = new StringBuilder();
41+
if (userMessage.content() != null && !userMessage.content().isEmpty()) {
42+
for (ThreadUserMessagePart part : userMessage.content()) {
43+
if (part instanceof TextMessagePart textContentPart) {
44+
textBuilder.append(textContentPart.text());
45+
} else if (part instanceof FileMessagePart fileMessagePart) {
46+
parts.add(toPart(fileMessagePart.mimeType(), fileMessagePart.data()));
47+
}
48+
}
49+
}
50+
if (userMessage.attachments() != null && !userMessage.attachments().isEmpty()) {
51+
for (Attachment attachment : userMessage.attachments()) {
52+
var mimeType = attachment.contentType();
53+
if (attachment.content() != null && !attachment.content().isEmpty()) {
54+
for (ThreadUserMessagePart part : attachment.content()) {
55+
if (part instanceof ImageMessagePart imageMessagePart) {
56+
parts.add(toPart(mimeType, imageMessagePart.image()));
57+
} else if (part instanceof FileMessagePart fileMessagePart) {
58+
parts.add(toPart(mimeType, fileMessagePart.data()));
59+
}
60+
}
61+
}
62+
}
63+
}
64+
parts.add(Part.fromText(textBuilder.toString()));
65+
} else if (message instanceof ThreadAssistantMessage assistantMessage) {
66+
var textBuilder = new StringBuilder();
67+
for (ThreadAssistantMessagePart part : assistantMessage.content()) {
68+
if (part instanceof TextMessagePart textContentPart) {
69+
textBuilder.append(textContentPart.text());
70+
}
71+
}
72+
parts.add(Part.fromText(textBuilder.toString()));
73+
}
74+
}
75+
return Content.fromParts(parts.toArray(new Part[0]));
76+
}
77+
78+
/**
79+
* Convert a {@linkplain Content} to {@linkplain ChatAgentResponse}
80+
*
81+
* @param content {@linkplain Content}
82+
* @return {@linkplain ChatAgentResponse} of chat agent
83+
*/
84+
public static ChatAgentResponse toResponse(Content content) {
85+
var messageParts = new ArrayList<ThreadAssistantMessagePart>();
86+
if (content.parts().isPresent()) {
87+
for (Part part : content.parts().get()) {
88+
if (part.thought().isPresent() && part.thought().get()) {
89+
part.text().ifPresent(text -> messageParts.add(new ReasoningMessagePart(text)));
90+
} else {
91+
part.text().ifPresent(text -> messageParts.add(new TextMessagePart(text)));
92+
}
93+
if (part.inlineData().isPresent()) {
94+
var blob = part.inlineData().get();
95+
if (blob.mimeType().isPresent() && blob.data().isPresent()) {
96+
var mimeType = blob.mimeType().get();
97+
var data = blob.data().get();
98+
if (isImage(mimeType)) {
99+
messageParts.add(new ImageMessagePart(fromMediaData(mimeType, data)));
100+
} else {
101+
messageParts.add(new FileMessagePart(fromMediaData(mimeType, data), mimeType));
102+
}
103+
}
104+
}
105+
}
106+
}
107+
return new ChatAgentResponse(messageParts);
108+
}
109+
110+
private static Part toPart(String mimeType, String data) {
111+
return Part.fromBytes(Base64.getEncoder().encode(data.getBytes(StandardCharsets.UTF_8)),
112+
mimeType);
113+
}
114+
115+
private static boolean isImage(String mimeType) {
116+
return mimeType.startsWith("image/");
117+
}
118+
119+
private static String fromMediaData(String mimeType, byte[] data) {
120+
if (isImage(mimeType)) {
121+
return String.format("data:%s;base64,%s", mimeType,
122+
Base64.getEncoder().encodeToString(data));
123+
} else {
124+
return Base64.getEncoder().encodeToString(data);
125+
}
126+
}
127+
128+
/**
129+
* Convert a stream of {@linkplain Content} to a stream of {@linkplain ChatAgentResponse} using
130+
* Server-sent Events
131+
*
132+
* @param content Stream of {@linkplain Content}
133+
* @return Stream of {@linkplain ChatAgentResponse}
134+
*/
135+
public static Flowable<ServerSentEvent<ChatAgentResponse>> toStreamingResponse(
136+
Flowable<Content> content) {
137+
return content.map(ModelAdapter::toResponse)
138+
.filter(ChatAgentResponse::hasContent)
139+
.map(response -> ServerSentEvent.<ChatAgentResponse>builder()
140+
.data(response)
141+
.build());
142+
}
143+
}

pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66
<groupId>com.javaaidev.llmagentspec</groupId>
77
<artifactId>llm-agent-spec</artifactId>
8-
<version>0.5.0</version>
8+
<version>0.6.0</version>
99
<packaging>pom</packaging>
1010
<name>LLM Agent Spec</name>
1111
<description>Spec of LLM Agents</description>
@@ -32,6 +32,7 @@
3232
<modules>
3333
<module>spring-ai-adapter</module>
3434
<module>chat-agent-model</module>
35+
<module>google-genai-adapter</module>
3536
</modules>
3637

3738
<scm>

spring-ai-adapter/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>com.javaaidev.llmagentspec</groupId>
88
<artifactId>llm-agent-spec</artifactId>
9-
<version>0.5.0</version>
9+
<version>0.6.0</version>
1010
</parent>
1111

1212
<artifactId>spring-ai-adapter</artifactId>
@@ -18,7 +18,7 @@
1818
<maven.compiler.source>${java.version}</maven.compiler.source>
1919
<maven.compiler.target>${java.version}</maven.compiler.target>
2020
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
21-
<spring-ai.version>1.1.0</spring-ai.version>
21+
<spring-ai.version>1.1.1</spring-ai.version>
2222
<spring.version>6.2.14</spring.version>
2323
</properties>
2424

0 commit comments

Comments
 (0)