Skip to content

Commit 1b8dfdb

Browse files
authored
Merge branch 'main' into tutorials-JBang
2 parents fc4f6b2 + b34feaa commit 1b8dfdb

File tree

8 files changed

+267
-23
lines changed

8 files changed

+267
-23
lines changed

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"redhat.java",
4+
"vscjava.vscode-java-pack",
5+
"josevseb.google-java-format-for-vs-code"
6+
]
7+
}

.vscode/settings.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
// formatOnType and formatOnPaste is a very bad idea for slow formatters
3+
// (such as an external Google Java Format invocation exec), so just on Save:
4+
"editor.formatOnSave": true,
5+
"editor.formatOnType": false,
6+
"editor.formatOnPaste": false,
7+
8+
"files.insertFinalNewline": true,
9+
"files.trimTrailingWhitespace": true,
10+
11+
"[java]": {
12+
"editor.tabSize": 2,
13+
// Format Java using https://github.com/google/google-java-format,
14+
// via https://github.com/JoseVSeb/google-java-format-for-vs-code
15+
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code",
16+
"editor.codeActionsOnSave": {
17+
// Used by at least JS as well as Java, so only overridden for [java]
18+
"source.organizeImports": "always",
19+
"source.addMissingImports": "never"
20+
}
21+
},
22+
// Keep this version in sync with the same version in pom.xml
23+
// NB: Changes to this are only taken into account on start-up, so need to restart.
24+
"java.format.settings.google.version": "1.27.0",
25+
// TODO https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3050
26+
"java.compile.nullAnalysis.mode": "automatic",
27+
"java.completion.importOrder": ["#", "", "javax", "java"], //# is static
28+
"java.completion.favoriteStaticMembers": ["com.google.common.truth.Truth.*"],
29+
"java.configuration.updateBuildConfiguration": "automatic",
30+
"java.import.maven.enabled": true
31+
}

contrib/langchain4j/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
<properties>
3333
<mcp-schema.version>0.10.0</mcp-schema.version>
34-
<google.genai.version>1.0.0</google.genai.version>
34+
<google.genai.version>1.8.0</google.genai.version>
3535
<junit.version>5.11.4</junit.version>
3636
<mockito.version>5.17.0</mockito.version>
3737
<langchain4j.version>1.1.0</langchain4j.version>
@@ -136,4 +136,4 @@
136136
<scope>test</scope>
137137
</dependency>
138138
</dependencies>
139-
</project>
139+
</project>

contrib/langchain4j/src/main/java/com/google/adk/models/langchain4j/LangChain4j.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,11 @@ private ChatRequest toChatRequest(LlmRequest llmRequest) {
224224
.mode()
225225
.ifPresent(
226226
functionMode -> {
227-
if (functionMode
228-
.knownEnum()
229-
.equals(FunctionCallingConfigMode.Known.AUTO)) {
227+
if (FunctionCallingConfigMode.Known.AUTO.equals(
228+
functionMode.knownEnum())) {
230229
requestBuilder.toolChoice(ToolChoice.AUTO);
231-
} else if (functionMode
232-
.knownEnum()
233-
.equals(FunctionCallingConfigMode.Known.ANY)) {
230+
} else if (FunctionCallingConfigMode.Known.ANY.equals(
231+
functionMode.knownEnum())) {
234232
// TODO check if it's the correct
235233
// mapping
236234
requestBuilder.toolChoice(ToolChoice.REQUIRED);
@@ -246,9 +244,8 @@ private ChatRequest toChatRequest(LlmRequest llmRequest) {
246244
toolSpecification.name()))
247245
.toList());
248246
});
249-
} else if (functionMode
250-
.knownEnum()
251-
.equals(FunctionCallingConfigMode.Known.NONE)) {
247+
} else if (FunctionCallingConfigMode.Known.NONE.equals(
248+
functionMode.knownEnum())) {
252249
requestBuilder.toolSpecifications(List.of());
253250
}
254251
});
@@ -349,7 +346,7 @@ private List<ChatMessage> toUserOrToolResultMessage(Content content) {
349346
.mimeType(mimeType)
350347
.build());
351348
} else if (mimeType.startsWith("text/")
352-
|| mimeType.equals("application/json")
349+
|| "application/json".equals(mimeType)
353350
|| mimeType.endsWith("+json")
354351
|| mimeType.endsWith("+xml")) {
355352
// TODO are there missing text based mime types?
@@ -454,7 +451,7 @@ private List<ToolSpecification> toToolSpecifications(LlmRequest llmRequest) {
454451
}
455452

456453
private JsonObjectSchema toParameters(Schema schema) {
457-
if (schema.type().isPresent() && schema.type().get().knownEnum().equals(Type.Known.OBJECT)) {
454+
if (schema.type().isPresent() && Type.Known.OBJECT.equals(schema.type().get().knownEnum())) {
458455
return JsonObjectSchema.builder()
459456
.addProperties(toProperties(schema))
460457
.required(schema.required().orElse(List.of()))
@@ -490,7 +487,7 @@ private JsonSchemaElement toJsonSchemaElement(Schema schema) {
490487
.items(toJsonSchemaElement(schema.items().orElseThrow()))
491488
.build();
492489
case OBJECT -> toParameters(schema);
493-
case TYPE_UNSPECIFIED ->
490+
default ->
494491
throw new UnsupportedFeatureException(
495492
"LangChain4jLlm does not support schema of type: " + type);
496493
};

dev/src/main/java/com/google/adk/web/AdkWebServer.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
@ComponentScan(basePackages = {"com.google.adk.web", "com.google.adk.web.config"})
126126
public class AdkWebServer implements WebMvcConfigurer {
127127

128+
private static AgentLoader AGENT_LOADER;
129+
128130
private static final Logger log = LoggerFactory.getLogger(AdkWebServer.class);
129131

130132
@Value("${adk.web.ui.dir:#{null}}")
@@ -175,23 +177,33 @@ public Map<String, BaseAgent> loadedAgentRegistry(
175177
}
176178

177179
try {
180+
// If AGENT_LOADER is set (by start()), use it
181+
if (AGENT_LOADER != null) {
182+
var staticAgents = AGENT_LOADER.loadAgents();
183+
agents.putAll(staticAgents);
184+
log.info("Loaded {} static agents: {}", staticAgents.size(), staticAgents.keySet());
185+
}
186+
178187
// Create and use compiler loader
179188
AgentCompilerLoader compilerLoader = new AgentCompilerLoader(props);
180189
Map<String, BaseAgent> compiledAgents = compilerLoader.loadAgents();
181190
agents.putAll(compiledAgents);
182-
log.info("Loaded {} compiled agents: {}", compiledAgents.size(), compiledAgents.keySet());
191+
if (!compiledAgents.isEmpty())
192+
log.info("Loaded {} compiled agents: {}", compiledAgents.size(), compiledAgents.keySet());
183193

184194
// Create and use YAML hot loader
185195
AgentYamlHotLoader yamlLoader =
186196
new AgentYamlHotLoader(props, agents, runnerService, hotReloadingEnabled);
187197
Map<String, BaseAgent> yamlAgents = yamlLoader.loadAgents();
188198
agents.putAll(yamlAgents);
189-
log.info("Loaded {} YAML agents: {}", yamlAgents.size(), yamlAgents.keySet());
199+
if (!yamlAgents.isEmpty()) {
200+
log.info("Loaded {} YAML agents: {}", yamlAgents.size(), yamlAgents.keySet());
190201

191-
// Start hot-reloading
192-
if (yamlLoader.supportsHotReloading()) {
193-
yamlLoader.start();
194-
log.info("Started hot-reloading for YAML agents");
202+
// Start hot-reloading
203+
if (yamlLoader.supportsHotReloading()) {
204+
yamlLoader.start();
205+
log.info("Started hot-reloading for YAML agents");
206+
}
195207
}
196208

197209
return agents;
@@ -653,7 +665,7 @@ public AgentController(
653665
this.apiServerSpanExporter = apiServerSpanExporter;
654666
this.runnerService = runnerService;
655667
log.info(
656-
"AgentController initialized with {} dynamic agents: {}",
668+
"AgentController initialized with {} agents: {}",
657669
agentRegistry.size(),
658670
agentRegistry.keySet());
659671
if (agentRegistry.isEmpty()) {
@@ -718,7 +730,7 @@ private Session findSessionOrThrow(String appName, String userId, String session
718730
*/
719731
@GetMapping("/list-apps")
720732
public List<String> listApps() {
721-
log.info("Listing apps from dynamic registry. Found: {}", agentRegistry.keySet());
733+
log.info("Listing apps from registry. Found: {}", agentRegistry.keySet());
722734
List<String> appNames = new ArrayList<>(agentRegistry.keySet());
723735
Collections.sort(appNames);
724736
return appNames;
@@ -1871,4 +1883,13 @@ public static void main(String[] args) {
18711883
SpringApplication.run(AdkWebServer.class, args);
18721884
log.info("AdkWebServer application started successfully.");
18731885
}
1886+
1887+
// TODO(vorburger): #later return Closeable, which can stop the server (and resets static)
1888+
public static synchronized void start(BaseAgent... agents) {
1889+
if (AGENT_LOADER != null) {
1890+
throw new IllegalStateException("AdkWebServer can only be started once.");
1891+
}
1892+
AGENT_LOADER = new AgentStaticLoader(agents);
1893+
main(new String[0]);
1894+
}
18741895
}

dev/src/main/java/com/google/adk/web/AgentLoader.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,7 @@ default boolean supportsHotReloading() {
6969
*
7070
* @return A string description of the loader type
7171
*/
72-
String getLoaderType();
72+
default String getLoaderType() {
73+
return getClass().getSimpleName();
74+
}
7375
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.adk.web;
17+
18+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
19+
import static java.util.Arrays.stream;
20+
import static java.util.function.Function.identity;
21+
22+
import com.google.adk.agents.BaseAgent;
23+
import com.google.common.collect.ImmutableMap;
24+
import java.io.IOException;
25+
import java.util.Map;
26+
27+
/**
28+
* Static Agent Loader.
29+
*
30+
* @author <a href="Michael Vorburger.ch">http://www.vorburger.ch/</a>
31+
*/
32+
class AgentStaticLoader implements AgentLoader {
33+
34+
private final ImmutableMap<String, BaseAgent> agents;
35+
36+
AgentStaticLoader(BaseAgent... agents) {
37+
this.agents = stream(agents).collect(toImmutableMap(BaseAgent::name, identity()));
38+
}
39+
40+
@Override
41+
public Map<String, BaseAgent> loadAgents() throws IOException {
42+
return agents;
43+
}
44+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.web;
18+
19+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
20+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
21+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
22+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
23+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
24+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
25+
26+
import org.hamcrest.Matchers;
27+
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
30+
import org.springframework.boot.test.context.SpringBootTest;
31+
import org.springframework.http.MediaType;
32+
import org.springframework.test.web.servlet.MockMvc;
33+
34+
/**
35+
* Integration tests for the {@link AdkWebServer}.
36+
*
37+
* <p>These tests use MockMvc to simulate HTTP requests and then verify the expected responses from
38+
* the ADK API server.
39+
*
40+
* @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a>, with Google Gemini Code
41+
* Assist in Agent mode
42+
*/
43+
@SpringBootTest
44+
@AutoConfigureMockMvc
45+
public class AdkWebServerTest {
46+
47+
@Autowired private MockMvc mockMvc;
48+
49+
@Test
50+
public void listApps_shouldReturnOkAndEmptyList() throws Exception {
51+
mockMvc.perform(get("/list-apps")).andExpect(status().isOk()).andExpect(content().json("[]"));
52+
}
53+
54+
@Test
55+
public void createSession_shouldReturnCreated() throws Exception {
56+
var result =
57+
mockMvc
58+
.perform(
59+
post("/apps/test-app/users/test-user/sessions")
60+
.contentType(MediaType.APPLICATION_JSON)
61+
.content("{}"))
62+
.andExpect(status().isOk())
63+
.andExpect(jsonPath("$.appName", Matchers.is("test-app")))
64+
.andExpect(jsonPath("$.userId", Matchers.is("test-user")))
65+
.andReturn();
66+
67+
var responseBody = result.getResponse().getContentAsString();
68+
var sessionId = com.jayway.jsonpath.JsonPath.read(responseBody, "$.id");
69+
mockMvc.perform(delete("/apps/test-app/users/test-user/sessions/" + sessionId));
70+
}
71+
72+
@Test
73+
public void createSessionWithId_shouldReturnCreated() throws Exception {
74+
try {
75+
mockMvc
76+
.perform(
77+
post("/apps/test-app/users/test-user/sessions/test-session")
78+
.contentType(MediaType.APPLICATION_JSON)
79+
.content("{}"))
80+
.andExpect(status().isOk())
81+
.andExpect(jsonPath("$.appName", Matchers.is("test-app")))
82+
.andExpect(jsonPath("$.userId", Matchers.is("test-user")))
83+
.andExpect(jsonPath("$.id", Matchers.is("test-session")));
84+
} finally {
85+
mockMvc.perform(delete("/apps/test-app/users/test-user/sessions/test-session"));
86+
}
87+
}
88+
89+
@Test
90+
public void deleteSession_shouldReturnNoContent() throws Exception {
91+
mockMvc.perform(
92+
post("/apps/test-app/users/test-user/sessions/test-session-to-delete")
93+
.contentType(MediaType.APPLICATION_JSON)
94+
.content("{}"));
95+
96+
mockMvc
97+
.perform(delete("/apps/test-app/users/test-user/sessions/test-session-to-delete"))
98+
.andExpect(status().isNoContent());
99+
}
100+
101+
@Test
102+
public void getSession_shouldReturnOk() throws Exception {
103+
mockMvc.perform(
104+
post("/apps/test-app/users/test-user/sessions/test-session")
105+
.contentType(MediaType.APPLICATION_JSON)
106+
.content("{}"));
107+
108+
try {
109+
mockMvc
110+
.perform(get("/apps/test-app/users/test-user/sessions/test-session"))
111+
.andExpect(status().isOk())
112+
.andExpect(jsonPath("$.appName", Matchers.is("test-app")))
113+
.andExpect(jsonPath("$.userId", Matchers.is("test-user")))
114+
.andExpect(jsonPath("$.id", Matchers.is("test-session")));
115+
} finally {
116+
mockMvc.perform(delete("/apps/test-app/users/test-user/sessions/test-session"));
117+
}
118+
}
119+
120+
@Test
121+
public void listSessions_shouldReturnOk() throws Exception {
122+
mockMvc.perform(
123+
post("/apps/test-app/users/test-user/sessions/test-session-1")
124+
.contentType(MediaType.APPLICATION_JSON)
125+
.content("{}"));
126+
mockMvc.perform(
127+
post("/apps/test-app/users/test-user/sessions/test-session-2")
128+
.contentType(MediaType.APPLICATION_JSON)
129+
.content("{}"));
130+
131+
mockMvc
132+
.perform(get("/apps/test-app/users/test-user/sessions"))
133+
.andExpect(status().isOk())
134+
.andExpect(
135+
jsonPath(
136+
"$[?(@.id == 'test-session-1' || @.id == 'test-session-2')].id",
137+
Matchers.containsInAnyOrder("test-session-1", "test-session-2")));
138+
139+
mockMvc.perform(delete("/apps/test-app/users/test-user/sessions/test-session-1"));
140+
mockMvc.perform(delete("/apps/test-app/users/test-user/sessions/test-session-2"));
141+
}
142+
}

0 commit comments

Comments
 (0)