Skip to content

Commit d57f9f6

Browse files
Jacksunweicopybara-github
authored andcommitted
feat(config): Adds ConfigRegistry for loading objects in yaml config
PiperOrigin-RevId: 794711316
1 parent 1945fad commit d57f9f6

File tree

2 files changed

+325
-0
lines changed

2 files changed

+325
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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.utils;
18+
19+
import com.google.adk.tools.BuiltInCodeExecutionTool;
20+
import com.google.adk.tools.GoogleSearchTool;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
import java.util.concurrent.ConcurrentHashMap;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
/**
28+
* A registry for storing and retrieving ADK instances by name.
29+
*
30+
* <p>This class provides a base registry with common ADK components and is designed to be extended
31+
* by users who want to add their own pre-wired entries. The registry is thread-safe and supports
32+
* storing any type of object.
33+
*
34+
* <p>Base pre-wired entries include:
35+
*
36+
* <ul>
37+
* <li>"google_search" - GoogleSearchTool instance
38+
* <li>"code_execution" - BuiltInCodeExecutionTool instance
39+
* <li>"exit_loop" - ExitLoopTool instance
40+
* </ul>
41+
*
42+
* <p>Example usage:
43+
*
44+
* <pre>{@code
45+
* // Extend ConfigRegistry to add custom pre-wired entries
46+
* public class MyConfigRegistry extends ConfigRegistry {
47+
* public MyConfigRegistry() {
48+
* super(); // Initialize base pre-wired entries
49+
* register("my_custom_tool", new MyCustomTool());
50+
* register("my_agent", new MyCustomAgent());
51+
* }
52+
* }
53+
* }</pre>
54+
*
55+
* // Use the custom registry TODO: add usage sample with mvn google-adk@web
56+
*/
57+
public class ConfigRegistry {
58+
59+
private static final Logger logger = LoggerFactory.getLogger(ConfigRegistry.class);
60+
61+
private final Map<String, Object> registry = new ConcurrentHashMap<>();
62+
63+
public ConfigRegistry() {
64+
initializePreWiredEntries();
65+
}
66+
67+
/** Initializes the registry with base pre-wired ADK instances. */
68+
private void initializePreWiredEntries() {
69+
try {
70+
registry.put("google_search", new GoogleSearchTool());
71+
registry.put("code_execution", new BuiltInCodeExecutionTool());
72+
73+
logger.debug("Initialized base pre-wired entries in ConfigRegistry");
74+
} catch (Exception e) {
75+
logger.warn("Failed to initialize some base pre-wired entries", e);
76+
}
77+
}
78+
79+
/**
80+
* Registers an object with the given name. This can override pre-wired entries.
81+
*
82+
* @param name the name to associate with the object
83+
* @param value the object to register (can be an instance, class, function, etc.)
84+
* @throws IllegalArgumentException if name is null or empty, or if value is null
85+
*/
86+
public void register(String name, Object value) {
87+
if (name == null || name.trim().isEmpty()) {
88+
throw new IllegalArgumentException("Name cannot be null or empty");
89+
}
90+
if (value == null) {
91+
throw new IllegalArgumentException("Value cannot be null");
92+
}
93+
94+
Object previous = registry.put(name, value);
95+
if (previous != null) {
96+
logger.info(
97+
"Overriding existing registration for name: {} (was: {}, now: {})",
98+
name,
99+
previous.getClass().getSimpleName(),
100+
value != null ? value.getClass().getSimpleName() : "null");
101+
} else {
102+
logger.debug(
103+
"Registered new object of type {} with name: {}",
104+
value != null ? value.getClass().getSimpleName() : "null",
105+
name);
106+
}
107+
}
108+
109+
/**
110+
* Retrieves an object by name and attempts to cast it to the specified type.
111+
*
112+
* @param name the name of the object to retrieve
113+
* @param type the expected type of the object
114+
* @param <T> the type parameter
115+
* @return an Optional containing the object if found and castable to the specified type, or an
116+
* empty Optional otherwise
117+
*/
118+
public <T> Optional<T> get(String name, Class<T> type) {
119+
return get(name)
120+
.filter(
121+
value -> {
122+
if (type.isInstance(value)) {
123+
return true;
124+
} else {
125+
logger.warn(
126+
"Object with name '{}' is of type {} but expected type {}",
127+
name,
128+
value.getClass().getSimpleName(),
129+
type.getSimpleName());
130+
return false;
131+
}
132+
})
133+
.map(type::cast);
134+
}
135+
136+
/**
137+
* Retrieves an object by name without type checking.
138+
*
139+
* @param name the name of the object to retrieve
140+
* @return an Optional containing the object if found, or an empty Optional otherwise
141+
*/
142+
public Optional<Object> get(String name) {
143+
if (name == null || name.trim().isEmpty()) {
144+
return Optional.empty();
145+
}
146+
147+
return Optional.ofNullable(registry.get(name));
148+
}
149+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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.utils;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertThrows;
21+
22+
import com.google.adk.tools.BuiltInCodeExecutionTool;
23+
import com.google.adk.tools.GoogleSearchTool;
24+
import java.util.Optional;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.junit.runners.JUnit4;
28+
29+
@RunWith(JUnit4.class)
30+
public final class ConfigRegistryTest {
31+
32+
@Test
33+
public void testPreWiredEntries() {
34+
ConfigRegistry registry = new ConfigRegistry();
35+
36+
Optional<GoogleSearchTool> searchTool = registry.get("google_search", GoogleSearchTool.class);
37+
assertThat(searchTool).isPresent();
38+
39+
Optional<BuiltInCodeExecutionTool> codeTool =
40+
registry.get("code_execution", BuiltInCodeExecutionTool.class);
41+
assertThat(codeTool).isPresent();
42+
}
43+
44+
@Test
45+
public void testRegisterAndGet() {
46+
ConfigRegistry registry = new ConfigRegistry();
47+
String testValue = "test value";
48+
49+
registry.register("test_key", testValue);
50+
51+
Optional<String> result = registry.get("test_key", String.class);
52+
assertThat(result).hasValue(testValue);
53+
}
54+
55+
@Test
56+
public void testGetWithoutType() {
57+
ConfigRegistry registry = new ConfigRegistry();
58+
String testValue = "test value";
59+
60+
registry.register("test_key", testValue);
61+
62+
Optional<Object> result = registry.get("test_key");
63+
assertThat(result).hasValue(testValue);
64+
}
65+
66+
@Test
67+
public void testGetNonExistentKey() {
68+
ConfigRegistry registry = new ConfigRegistry();
69+
70+
Optional<String> result = registry.get("non_existent", String.class);
71+
assertThat(result).isEmpty();
72+
73+
Optional<Object> resultNoType = registry.get("non_existent");
74+
assertThat(resultNoType).isEmpty();
75+
}
76+
77+
@Test
78+
public void testGetWithWrongType() {
79+
ConfigRegistry registry = new ConfigRegistry();
80+
registry.register("test_key", "string value");
81+
82+
Optional<Integer> result = registry.get("test_key", Integer.class);
83+
assertThat(result).isEmpty();
84+
}
85+
86+
@Test
87+
public void testOverridePreWiredEntry() {
88+
ConfigRegistry registry = new ConfigRegistry();
89+
String customSearchTool = "custom search tool";
90+
91+
registry.register("google_search", customSearchTool);
92+
93+
Optional<String> result = registry.get("google_search", String.class);
94+
assertThat(result).hasValue(customSearchTool);
95+
96+
Optional<GoogleSearchTool> originalTool = registry.get("google_search", GoogleSearchTool.class);
97+
assertThat(originalTool).isEmpty();
98+
}
99+
100+
@Test
101+
public void testRegisterWithNullName() {
102+
ConfigRegistry registry = new ConfigRegistry();
103+
104+
IllegalArgumentException thrown =
105+
assertThrows(IllegalArgumentException.class, () -> registry.register(null, "value"));
106+
107+
assertThat(thrown.getMessage()).contains("Name cannot be null or empty");
108+
}
109+
110+
@Test
111+
public void testRegisterWithEmptyName() {
112+
ConfigRegistry registry = new ConfigRegistry();
113+
114+
IllegalArgumentException thrown1 =
115+
assertThrows(IllegalArgumentException.class, () -> registry.register("", "value"));
116+
IllegalArgumentException thrown2 =
117+
assertThrows(IllegalArgumentException.class, () -> registry.register(" ", "value"));
118+
119+
assertThat(thrown1.getMessage()).contains("Name cannot be null or empty");
120+
assertThat(thrown2.getMessage()).contains("Name cannot be null or empty");
121+
}
122+
123+
@Test
124+
public void testGetWithNullName() {
125+
ConfigRegistry registry = new ConfigRegistry();
126+
127+
Optional<String> result = registry.get(null, String.class);
128+
assertThat(result).isEmpty();
129+
130+
Optional<Object> resultNoType = registry.get(null);
131+
assertThat(resultNoType).isEmpty();
132+
}
133+
134+
@Test
135+
public void testGetWithEmptyName() {
136+
ConfigRegistry registry = new ConfigRegistry();
137+
138+
Optional<String> result = registry.get("", String.class);
139+
assertThat(result).isEmpty();
140+
141+
Optional<String> resultWhitespace = registry.get(" ", String.class);
142+
assertThat(resultWhitespace).isEmpty();
143+
}
144+
145+
@Test
146+
public void testRegisterNullValue() {
147+
ConfigRegistry registry = new ConfigRegistry();
148+
149+
IllegalArgumentException thrown =
150+
assertThrows(IllegalArgumentException.class, () -> registry.register("null_test", null));
151+
152+
assertThat(thrown.getMessage()).contains("Value cannot be null");
153+
}
154+
155+
@Test
156+
public void testSubclassExtension() {
157+
class CustomConfigRegistry extends ConfigRegistry {
158+
public CustomConfigRegistry() {
159+
super();
160+
register("custom_tool", "my custom tool");
161+
register("custom_agent", new Object());
162+
}
163+
}
164+
165+
CustomConfigRegistry registry = new CustomConfigRegistry();
166+
167+
Optional<GoogleSearchTool> prewiredTool = registry.get("google_search", GoogleSearchTool.class);
168+
assertThat(prewiredTool).isPresent();
169+
170+
Optional<String> customTool = registry.get("custom_tool", String.class);
171+
assertThat(customTool).hasValue("my custom tool");
172+
173+
Optional<Object> customAgent = registry.get("custom_agent");
174+
assertThat(customAgent).isPresent();
175+
}
176+
}

0 commit comments

Comments
 (0)