|
54 | 54 | import com.google.adk.tools.BaseTool.ToolArgsConfig; |
55 | 55 | import com.google.adk.tools.BaseTool.ToolConfig; |
56 | 56 | import com.google.adk.tools.BaseToolset; |
57 | | -import com.google.adk.utils.ComponentRegistry; |
58 | 57 | import com.google.adk.tools.internal.ToolConstraints; |
| 58 | +import com.google.adk.utils.ComponentRegistry; |
59 | 59 | import com.google.common.base.Preconditions; |
60 | 60 | import com.google.common.collect.ImmutableList; |
61 | 61 | import com.google.errorprone.annotations.CanIgnoreReturnValue; |
@@ -613,14 +613,9 @@ protected void validate() { |
613 | 613 | } |
614 | 614 |
|
615 | 615 | public LlmAgent build() { |
| 616 | + validate(); |
616 | 617 | LlmAgent built = new LlmAgent(this); |
617 | | - // New Guardrail Check |
618 | | - if (Boolean.getBoolean("adk.enableToolConstraints")) { |
619 | | - try { |
620 | | - ToolConstraints.validateAgentTree(built); |
621 | | - } catch (NullPointerException ignored) { |
622 | | - } |
623 | | - } |
| 618 | + ToolConstraints.validateAgentTree(built); |
624 | 619 | return built; |
625 | 620 | } |
626 | 621 | } |
@@ -1190,307 +1185,4 @@ static BaseTool resolveInstanceViaReflection(String toolName) throws Exception { |
1190 | 1185 | } |
1191 | 1186 | return null; |
1192 | 1187 | } |
1193 | | - |
1194 | | - /** |
1195 | | - * Creates an LlmAgent from configuration. |
1196 | | - * |
1197 | | - * @param config the agent configuration |
1198 | | - * @param configAbsPath The absolute path to the agent config file. This is needed for resolving |
1199 | | - * relative paths for e.g. tools. |
1200 | | - * @return the configured LlmAgent |
1201 | | - * @throws ConfigurationException if the configuration is invalid |
1202 | | - * <p>TODO: Config agent features are not yet ready for public use. |
1203 | | - */ |
1204 | | - public static LlmAgent fromConfig(LlmAgentConfig config, String configAbsPath) |
1205 | | - throws ConfigurationException { |
1206 | | - logger.debug("Creating LlmAgent from config: {}", config.name()); |
1207 | | - |
1208 | | - // Validate required fields |
1209 | | - if (config.name() == null || config.name().trim().isEmpty()) { |
1210 | | - throw new ConfigurationException("Agent name is required"); |
1211 | | - } |
1212 | | - |
1213 | | - if (config.instruction() == null || config.instruction().trim().isEmpty()) { |
1214 | | - throw new ConfigurationException("Agent instruction is required"); |
1215 | | - } |
1216 | | - |
1217 | | - // Create builder with required fields |
1218 | | - Builder builder = |
1219 | | - LlmAgent.builder() |
1220 | | - .name(config.name()) |
1221 | | - .description(nullToEmpty(config.description())) |
1222 | | - .instruction(config.instruction()); |
1223 | | - |
1224 | | - if (config.model() != null && !config.model().trim().isEmpty()) { |
1225 | | - builder.model(config.model()); |
1226 | | - } |
1227 | | - |
1228 | | - try { |
1229 | | - if (config.tools() != null) { |
1230 | | - builder.tools(resolveTools(config.tools(), configAbsPath)); |
1231 | | - } |
1232 | | - } catch (ConfigurationException e) { |
1233 | | - throw new ConfigurationException("Error resolving tools for agent " + config.name(), e); |
1234 | | - } |
1235 | | - |
1236 | | - // Set optional transfer configuration |
1237 | | - if (config.disallowTransferToParent() != null) { |
1238 | | - builder.disallowTransferToParent(config.disallowTransferToParent()); |
1239 | | - } |
1240 | | - |
1241 | | - if (config.disallowTransferToPeers() != null) { |
1242 | | - builder.disallowTransferToPeers(config.disallowTransferToPeers()); |
1243 | | - } |
1244 | | - |
1245 | | - // Set optional output key |
1246 | | - if (config.outputKey() != null && !config.outputKey().trim().isEmpty()) { |
1247 | | - builder.outputKey(config.outputKey()); |
1248 | | - } |
1249 | | - |
1250 | | - // Build and return the agent |
1251 | | - LlmAgent agent = builder.build(); |
1252 | | - logger.info("Successfully created LlmAgent: {}", agent.name()); |
1253 | | - |
1254 | | - return agent; |
1255 | | - } |
1256 | | - |
1257 | | - /** |
1258 | | - * Resolves a list of tool configurations into {@link BaseTool} instances. |
1259 | | - * |
1260 | | - * <p>This method is only for use by Agent Development Kit. |
1261 | | - * |
1262 | | - * @param toolConfigs The list of tool configurations to resolve. |
1263 | | - * @param configAbsPath The absolute path to the agent config file currently being processed. This |
1264 | | - * path can be used to resolve relative paths for tool configurations, if necessary. |
1265 | | - * @return An immutable list of resolved {@link BaseTool} instances. |
1266 | | - * @throws ConfigurationException if any tool configuration is invalid (e.g., missing name), if a |
1267 | | - * tool cannot be found by its name or class, or if tool instantiation fails. |
1268 | | - */ |
1269 | | - static ImmutableList<BaseTool> resolveTools(List<ToolConfig> toolConfigs, String configAbsPath) |
1270 | | - throws ConfigurationException { |
1271 | | - |
1272 | | - if (toolConfigs == null || toolConfigs.isEmpty()) { |
1273 | | - return ImmutableList.of(); |
1274 | | - } |
1275 | | - |
1276 | | - ImmutableList.Builder<BaseTool> resolvedTools = ImmutableList.builder(); |
1277 | | - |
1278 | | - for (ToolConfig toolConfig : toolConfigs) { |
1279 | | - try { |
1280 | | - if (isNullOrEmpty(toolConfig.name())) { |
1281 | | - throw new ConfigurationException("Tool name cannot be empty"); |
1282 | | - } |
1283 | | - |
1284 | | - String toolName = toolConfig.name().trim(); |
1285 | | - |
1286 | | - // Option 1: Try to resolve as a tool instance |
1287 | | - BaseTool tool = resolveToolInstance(toolName); |
1288 | | - if (tool != null) { |
1289 | | - resolvedTools.add(tool); |
1290 | | - logger.debug("Successfully resolved tool instance: {}", toolName); |
1291 | | - continue; |
1292 | | - } |
1293 | | - |
1294 | | - // Option 2: Try to resolve as a tool class (with or without args) |
1295 | | - BaseTool toolFromClass = resolveToolFromClass(toolName, toolConfig.args()); |
1296 | | - if (toolFromClass != null) { |
1297 | | - resolvedTools.add(toolFromClass); |
1298 | | - logger.debug("Successfully resolved tool from class: {}", toolName); |
1299 | | - continue; |
1300 | | - } |
1301 | | - |
1302 | | - throw new ConfigurationException("Tool not found: " + toolName); |
1303 | | - |
1304 | | - } catch (Exception e) { |
1305 | | - String errorMsg = "Failed to resolve tool: " + toolConfig.name(); |
1306 | | - logger.error(errorMsg, e); |
1307 | | - throw new ConfigurationException(errorMsg, e); |
1308 | | - } |
1309 | | - } |
1310 | | - |
1311 | | - return resolvedTools.build(); |
1312 | | - } |
1313 | | - |
1314 | | - /** |
1315 | | - * Resolves a tool instance by its unique name or its static field reference. |
1316 | | - * |
1317 | | - * <p>It first checks the {@link ComponentRegistry} for a registered tool instance. If not found, |
1318 | | - * and the name looks like a fully qualified Java name referencing a static field (e.g., |
1319 | | - * "com.google.mytools.MyToolClass.INSTANCE"), it attempts to resolve it via reflection using |
1320 | | - * {@link #resolveInstanceViaReflection(String)}. |
1321 | | - * |
1322 | | - * @param toolName The name of the tool or a static field reference (e.g., "myTool", |
1323 | | - * "com.google.mytools.MyToolClass.INSTANCE"). |
1324 | | - * @return The resolved tool instance, or {@code null} if the tool is not found in the registry |
1325 | | - * and cannot be resolved via reflection. |
1326 | | - */ |
1327 | | - @Nullable |
1328 | | - static BaseTool resolveToolInstance(String toolName) { |
1329 | | - ComponentRegistry registry = ComponentRegistry.getInstance(); |
1330 | | - |
1331 | | - // First try registry |
1332 | | - Optional<BaseTool> toolOpt = ComponentRegistry.resolveToolInstance(toolName); |
1333 | | - if (toolOpt.isPresent()) { |
1334 | | - return toolOpt.get(); |
1335 | | - } |
1336 | | - |
1337 | | - // If not in registry and looks like Java qualified name, try reflection |
1338 | | - if (isJavaQualifiedName(toolName)) { |
1339 | | - try { |
1340 | | - BaseTool tool = resolveInstanceViaReflection(toolName); |
1341 | | - if (tool != null) { |
1342 | | - registry.register(toolName, tool); |
1343 | | - logger.debug("Resolved and registered tool instance via reflection: {}", toolName); |
1344 | | - return tool; |
1345 | | - } |
1346 | | - } catch (Exception e) { |
1347 | | - logger.debug("Failed to resolve instance via reflection: {}", toolName, e); |
1348 | | - } |
1349 | | - } |
1350 | | - logger.debug("Could not resolve tool instance: {}", toolName); |
1351 | | - return null; |
1352 | | - } |
1353 | | - |
1354 | | - /** |
1355 | | - * Resolves a tool from a class name and optional arguments. |
1356 | | - * |
1357 | | - * <p>It attempts to load the class specified by {@code className}. If {@code args} are provided |
1358 | | - * and non-empty, it looks for a static factory method {@code fromConfig(ToolArgsConfig)} on the |
1359 | | - * class to instantiate the tool. If {@code args} are null or empty, it looks for a default |
1360 | | - * constructor. |
1361 | | - * |
1362 | | - * @param className The fully qualified name of the tool class to instantiate. |
1363 | | - * @param args Optional configuration arguments for tool creation. If provided, the class must |
1364 | | - * implement a static {@code fromConfig(ToolArgsConfig)} factory method. If null or empty, the |
1365 | | - * class must have a default constructor. |
1366 | | - * @return The instantiated tool instance, or {@code null} if the class cannot be found or loaded. |
1367 | | - * @throws ConfigurationException if {@code args} are provided but no {@code fromConfig} method |
1368 | | - * exists, if {@code args} are not provided but no default constructor exists, or if |
1369 | | - * instantiation via the factory method or constructor fails. |
1370 | | - */ |
1371 | | - @Nullable |
1372 | | - static BaseTool resolveToolFromClass(String className, ToolArgsConfig args) |
1373 | | - throws ConfigurationException { |
1374 | | - ComponentRegistry registry = ComponentRegistry.getInstance(); |
1375 | | - |
1376 | | - // First try registry for class |
1377 | | - Optional<Class<? extends BaseTool>> classOpt = ComponentRegistry.resolveToolClass(className); |
1378 | | - Class<? extends BaseTool> toolClass = null; |
1379 | | - |
1380 | | - if (classOpt.isPresent()) { |
1381 | | - toolClass = classOpt.get(); |
1382 | | - } else if (isJavaQualifiedName(className)) { |
1383 | | - // Try reflection to get class |
1384 | | - try { |
1385 | | - Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className); |
1386 | | - if (BaseTool.class.isAssignableFrom(clazz)) { |
1387 | | - toolClass = clazz.asSubclass(BaseTool.class); |
1388 | | - // Optimization: register for reuse |
1389 | | - registry.register(className, toolClass); |
1390 | | - logger.debug("Resolved and registered tool class via reflection: {}", className); |
1391 | | - } |
1392 | | - } catch (ClassNotFoundException e) { |
1393 | | - logger.debug("Failed to resolve class via reflection: {}", className, e); |
1394 | | - return null; |
1395 | | - } |
1396 | | - } |
1397 | | - |
1398 | | - if (toolClass == null) { |
1399 | | - return null; |
1400 | | - } |
1401 | | - |
1402 | | - // If args provided and not empty, try fromConfig method first |
1403 | | - if (args != null && !args.isEmpty()) { |
1404 | | - try { |
1405 | | - Method fromConfigMethod = toolClass.getMethod("fromConfig", ToolArgsConfig.class); |
1406 | | - Object instance = fromConfigMethod.invoke(null, args); |
1407 | | - if (instance instanceof BaseTool baseTool) { |
1408 | | - return baseTool; |
1409 | | - } |
1410 | | - } catch (NoSuchMethodException e) { |
1411 | | - throw new ConfigurationException( |
1412 | | - "Class " + className + " does not have fromConfig method but args were provided.", e); |
1413 | | - } catch (Exception e) { |
1414 | | - logger.error("Error calling fromConfig on class {}", className, e); |
1415 | | - throw new ConfigurationException("Error creating tool from class " + className, e); |
1416 | | - } |
1417 | | - } |
1418 | | - |
1419 | | - // No args provided or empty args, try default constructor |
1420 | | - try { |
1421 | | - Constructor<? extends BaseTool> constructor = toolClass.getDeclaredConstructor(); |
1422 | | - constructor.setAccessible(true); |
1423 | | - return constructor.newInstance(); |
1424 | | - } catch (NoSuchMethodException e) { |
1425 | | - throw new ConfigurationException( |
1426 | | - "Class " + className + " does not have a default constructor and no args were provided.", |
1427 | | - e); |
1428 | | - } catch (Exception e) { |
1429 | | - logger.error("Error calling default constructor on class {}", className, e); |
1430 | | - throw new ConfigurationException( |
1431 | | - "Error creating tool from class " + className + " using default constructor", e); |
1432 | | - } |
1433 | | - } |
1434 | | - |
1435 | | - /** |
1436 | | - * Checks if a string appears to be a Java fully qualified name, such as "com.google.adk.MyClass" |
1437 | | - * or "com.google.adk.MyClass.MY_FIELD". |
1438 | | - * |
1439 | | - * <p>It verifies that the name contains at least one dot ('.') and consists of characters valid |
1440 | | - * for Java identifiers and package names. |
1441 | | - * |
1442 | | - * @param name The string to check. |
1443 | | - * @return {@code true} if the string matches the pattern of a Java qualified name, {@code false} |
1444 | | - * otherwise. |
1445 | | - */ |
1446 | | - static boolean isJavaQualifiedName(String name) { |
1447 | | - if (name == null || name.trim().isEmpty()) { |
1448 | | - return false; |
1449 | | - } |
1450 | | - return name.contains(".") && name.matches("^[a-zA-Z_$][a-zA-Z0-9_.$]*$"); |
1451 | | - } |
1452 | | - |
1453 | | - /** |
1454 | | - * Resolves a {@link BaseTool} instance by attempting to access a public static field via |
1455 | | - * reflection. |
1456 | | - * |
1457 | | - * <p>This method expects {@code toolName} to be in the format |
1458 | | - * "com.google.package.ClassName.STATIC_FIELD_NAME", where "STATIC_FIELD_NAME" is the name of a |
1459 | | - * public static field in "com.google.package.ClassName" that holds a {@link BaseTool} instance. |
1460 | | - * |
1461 | | - * @param toolName The fully qualified name of a static field holding a tool instance. |
1462 | | - * @return The {@link BaseTool} instance, or {@code null} if {@code toolName} is not in the |
1463 | | - * expected format, or if the field is not found, not static, or not of type {@link BaseTool}. |
1464 | | - * @throws Exception if the class specified in {@code toolName} cannot be loaded, or if there is a |
1465 | | - * security manager preventing reflection, or if accessing the field causes an exception. |
1466 | | - */ |
1467 | | - @Nullable |
1468 | | - static BaseTool resolveInstanceViaReflection(String toolName) throws Exception { |
1469 | | - int lastDotIndex = toolName.lastIndexOf('.'); |
1470 | | - if (lastDotIndex == -1) { |
1471 | | - return null; |
1472 | | - } |
1473 | | - |
1474 | | - String className = toolName.substring(0, lastDotIndex); |
1475 | | - String fieldName = toolName.substring(lastDotIndex + 1); |
1476 | | - |
1477 | | - Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className); |
1478 | | - |
1479 | | - try { |
1480 | | - Field field = clazz.getField(fieldName); |
1481 | | - if (!Modifier.isStatic(field.getModifiers())) { |
1482 | | - logger.debug("Field {} in class {} is not static", fieldName, className); |
1483 | | - return null; |
1484 | | - } |
1485 | | - Object instance = field.get(null); |
1486 | | - if (instance instanceof BaseTool baseTool) { |
1487 | | - return baseTool; |
1488 | | - } else { |
1489 | | - logger.debug("Field {} in class {} is not a BaseTool instance", fieldName, className); |
1490 | | - } |
1491 | | - } catch (NoSuchFieldException e) { |
1492 | | - logger.debug("Field {} not found in class {}", fieldName, className); |
1493 | | - } |
1494 | | - return null; |
1495 | | - } |
1496 | 1188 | } |
0 commit comments