Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions core/src/main/java/com/google/adk/tools/Annotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.google.adk.tools;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.RECORD_COMPONENT;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -26,14 +28,20 @@
/** Annotations for tools. */
public final class Annotations {

/** The annotation for binding the 'Schema' input. */
@Target({METHOD, PARAMETER})
/**
* Describes a tool or its inputs. On object properties, overrides Jackson's name and required
* flag for tool schemas and tool JSON binding only; ordinary JSON serialization is unchanged.
*/
@Target({METHOD, PARAMETER, FIELD, RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
public @interface Schema {
/** Model-facing name; an empty property name retains Jackson's name or the Java default. */
String name() default "";

/** Description included in the generated tool schema. */
String description() default "";

/** Whether this input may be omitted from the generated schema's required list. */
boolean optional() default false;
}

Expand Down
34 changes: 27 additions & 7 deletions core/src/main/java/com/google/adk/tools/FunctionCallingUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ void addDefinition(JavaType type, Schema schema) {
*/
public static FunctionDeclaration buildFunctionDeclaration(
Method func, List<String> ignoreParams) {
return buildFunctionDeclaration(
func, ignoreParams, ToolSchemaObjectMapper.create(defaultObjectMapper));
}

static FunctionDeclaration buildFunctionDeclaration(
Method func, List<String> ignoreParams, ObjectMapper objectMapper) {
String name =
func.isAnnotationPresent(Annotations.Schema.class)
&& !func.getAnnotation(Annotations.Schema.class).name().isEmpty()
Expand All @@ -108,7 +114,7 @@ public static FunctionDeclaration buildFunctionDeclaration(
if (schema == null || !schema.optional()) {
required.add(paramName);
}
properties.put(paramName, buildSchemaFromParameter(param));
properties.put(paramName, buildSchemaFromParameter(param, objectMapper));
}
builder.parameters(
Schema.builder().required(required).properties(properties).type("OBJECT").build());
Expand All @@ -126,7 +132,11 @@ public static FunctionDeclaration buildFunctionDeclaration(
actualReturnType = parameterizedReturnType.getActualTypeArguments()[0];
}
}
builder.response(buildSchemaFromType(actualReturnType));
builder.response(
buildSchemaRecursive(
objectMapper.constructType(actualReturnType),
new SchemaGenerationContext(),
objectMapper));
}
return builder.build();
}
Expand All @@ -143,8 +153,12 @@ static FunctionDeclaration buildFunctionDeclaration(JsonBaseModel func, String d
return declaration;
}

private static Schema buildSchemaFromParameter(Parameter param) {
Schema schema = buildSchemaFromType(param.getParameterizedType());
private static Schema buildSchemaFromParameter(Parameter param, ObjectMapper objectMapper) {
Schema schema =
buildSchemaRecursive(
objectMapper.constructType(param.getParameterizedType()),
new SchemaGenerationContext(),
objectMapper);
if (param.isAnnotationPresent(Annotations.Schema.class)
&& !param.getAnnotation(Annotations.Schema.class).description().isEmpty()) {
return schema.toBuilder()
Expand Down Expand Up @@ -174,8 +188,9 @@ public static Schema buildSchemaFromType(Type type) {
* @throws IllegalArgumentException if a type is encountered that cannot be serialized by Jackson.
*/
public static Schema buildSchemaFromType(Type type, ObjectMapper objectMapper) {
ObjectMapper schemaMapper = ToolSchemaObjectMapper.create(objectMapper);
return buildSchemaRecursive(
objectMapper.constructType(type), new SchemaGenerationContext(), objectMapper);
schemaMapper.constructType(type), new SchemaGenerationContext(), schemaMapper);
}

/**
Expand Down Expand Up @@ -256,8 +271,13 @@ private static Schema buildSchemaRecursive(
for (BeanPropertyDefinition property : beanDescription.findProperties()) {
AnnotatedMember member = property.getPrimaryMember();
if (member != null) {
properties.put(
property.getName(), buildSchemaRecursive(member.getType(), context, objectMapper));
Schema propertySchema = buildSchemaRecursive(member.getType(), context, objectMapper);
Annotations.Schema annotation = member.getAnnotation(Annotations.Schema.class);
if (annotation != null && !annotation.description().isEmpty()) {
propertySchema =
propertySchema.toBuilder().description(annotation.description()).build();
}
properties.put(property.getName(), propertySchema);
if (property.isRequired()) {
required.add(property.getName());
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/com/google/adk/tools/FunctionTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,11 @@ protected FunctionTool(

this.instance = instance;
this.func = func;
this.objectMapper = ToolSchemaObjectMapper.create(objectMapper);
this.funcDeclaration =
FunctionCallingUtils.buildFunctionDeclaration(
this.func, ImmutableList.of("toolContext", "inputStream"));
this.func, ImmutableList.of("toolContext", "inputStream"), this.objectMapper);
this.requireConfirmation = requireConfirmation;
this.objectMapper = objectMapper;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.adk.tools;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;

/** Creates isolated mappers that use ADK annotations without mutating the application's mapper. */
final class ToolSchemaObjectMapper {

static ObjectMapper create(ObjectMapper source) {
ObjectMapper mapper = source.copy();
AnnotationIntrospector schema = new SchemaIntrospector();
mapper.setAnnotationIntrospectors(
AnnotationIntrospector.pair(
schema, source.getSerializationConfig().getAnnotationIntrospector()),
AnnotationIntrospector.pair(
schema, source.getDeserializationConfig().getAnnotationIntrospector()));
return mapper;
}

private static final class SchemaIntrospector extends NopAnnotationIntrospector {
@Override
public PropertyName findNameForSerialization(Annotated annotated) {
return schemaName(annotated);
}

@Override
public PropertyName findNameForDeserialization(Annotated annotated) {
return schemaName(annotated);
}

@Override
public Boolean hasRequiredMarker(AnnotatedMember member) {
Annotations.Schema schema = member.getAnnotation(Annotations.Schema.class);
return schema == null ? null : !schema.optional();
}

private static PropertyName schemaName(Annotated annotated) {
Annotations.Schema schema = annotated.getAnnotation(Annotations.Schema.class);
if (schema == null) {
return null;
}
// An empty ADK name keeps Jackson's explicit name, or the default Java property name.
return schema.name().isEmpty()
? PropertyName.USE_DEFAULT
: PropertyName.construct(schema.name());
}
}

private ToolSchemaObjectMapper() {}
}
Loading
Loading