mirror of
https://github.com/google/adk-docs.git
synced 2026-09-14 16:16:59 +08:00
8da5856bc5
* Add Kotlin snippet for filtering a toolset's tools
The Toolsets section had no Kotlin. ToolFilter and ToolPredicate arrived in
adk-kotlin 0.7.0 and give Kotlin something the other languages do not have: a
filter that receives the ReadonlyContext, so a toolset's tool list can depend on
session state or the current user.
The Python tab in the Simple Math Toolset example only gestures at this, in a
commented-out branch inside get_tools(). This snippet implements it, showing all
three states: no filter selects everything, allowList selects by name, and a
Predicate decides per invocation.
Placed in its own subsection rather than as a fourth tab on the Simple Math
Toolset example. That group is one worked example explained by five bullets --
an agent, a greet tool, name prefixing, a tool_context.state write and close() --
and a filtering snippet satisfies none of them. Kotlin cannot satisfy the prefix
bullet at all, since BaseTool.name is a val and adk-kotlin has no prefix
mechanism. A tab there would have left readers with four bullets that do not
describe the code above them.
Transcluded, so CI compiles and lints it. Verified beyond compiling: the tools
are generated by KSP, filtering returns the expected sets for all three cases,
and the tool bodies run -- addNumbers(7,3) -> {result=10}.
* feat: add Kotlin code snippets for tool definition and usage examples to ADK documentation
* refactor: move doc snippet markers above imports in Kotlin tool examples
* Match ExternalApprovalTool to the 0.8.0 BaseTool.run signature
adk-kotlin 0.8.0 widened `BaseTool.run`'s args from `Map<String, Any>` to
`Map<String, Any?>`, so the override in this snippet overrides nothing and the
class no longer implements its abstract member. `compileKotlin` fails with
"'run' overrides nothing" at MultiAgentExample.kt:61.
The break arrived on main with the 0.8.0 bump in #2143, not from this branch.
It surfaces here because the snippet runner builds the whole examples project,
while that PR's own check only compiled the files it changed - and it changed
no .kt files at all. Fixing it here because it blocks this PR; it is one
character and unrelated to the toolset filtering content.
* Correct the tool badges and drop the exclusivity from the filter heading
Four things from joefernandez's review:
- Heading shortened to "Filter tools in toolsets" as suggested.
- That shorter heading is conceptual, and the subsection carries a Kotlin-only
badge, which would repeat the exclusivity claim his b/548652184 is about.
Python, Java and TypeScript all filter toolsets by name or by a
context-aware predicate on BaseToolset, so the section now says so in a
sentence and the badge's title scopes the version to the Kotlin ToolFilter
API rather than to filtering as a concept.
- Page badge Kotlin v0.7.0 -> v0.1.0, and the Toolsets badge likewise. @Tool,
ToolContext and Toolset all exist at the v0.1.0 tag; only ToolFilter is new
in v0.7.0, and function-tools.md already carries v0.1.0.
- The Toolsets badge's bare "Java" span now reads v0.3.0, the first adk-java
release containing BaseToolset (added in a211ac4c, tagged v0.3.0).
123 lines
4.1 KiB
Kotlin
123 lines
4.1 KiB
Kotlin
/*
|
|
* 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.kt.examples.tools.overview
|
|
|
|
// --8<-- [start:init]
|
|
|
|
import com.google.adk.kt.agents.Instruction
|
|
import com.google.adk.kt.agents.LlmAgent
|
|
import com.google.adk.kt.agents.ReadonlyContext
|
|
import com.google.adk.kt.annotations.Param
|
|
import com.google.adk.kt.annotations.Tool
|
|
import com.google.adk.kt.models.Gemini
|
|
import com.google.adk.kt.tools.BaseTool
|
|
import com.google.adk.kt.tools.ToolContext
|
|
import com.google.adk.kt.tools.ToolFilter
|
|
import com.google.adk.kt.tools.Toolset
|
|
import com.google.adk.kt.tools.isToolSelected
|
|
|
|
/** The individual tools, exposed by the @Tool annotation. */
|
|
class MathTools {
|
|
/**
|
|
* Adds two integer numbers.
|
|
*/
|
|
@Tool
|
|
fun addNumbers(
|
|
@Param("The first number.") a: Int,
|
|
@Param("The second number.") b: Int,
|
|
context: ToolContext,
|
|
): Map<String, Any> {
|
|
// Example: recording something in the session state.
|
|
context.actions.stateDelta["last_math_operation"] = "addition"
|
|
return mapOf("status" to "success", "result" to a + b)
|
|
}
|
|
|
|
/**
|
|
* Subtracts the second number from the first.
|
|
*/
|
|
@Tool
|
|
fun subtractNumbers(
|
|
@Param("The first number.") a: Int,
|
|
@Param("The second number.") b: Int,
|
|
): Map<String, Any> = mapOf("status" to "success", "result" to a - b)
|
|
}
|
|
|
|
/** An individual tool, defined outside any toolset. */
|
|
class GreetTools {
|
|
/**
|
|
* Greets the user.
|
|
*/
|
|
@Tool
|
|
fun greetUser(
|
|
@Param("The name of the user to greet.") name: String,
|
|
): Map<String, String> {
|
|
println("Tool: greetUser called with name=$name")
|
|
return mapOf("greeting" to "Hello, $name!")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A toolset that narrows what it exposes with an optional [ToolFilter].
|
|
*
|
|
* A null filter selects every tool, so the filter is genuinely optional.
|
|
*/
|
|
class SimpleMathToolset(private val filter: ToolFilter? = null) : Toolset {
|
|
private val tools = MathTools().generatedTools()
|
|
|
|
override suspend fun getTools(readonlyContext: ReadonlyContext?): List<BaseTool> =
|
|
tools.filter { filter.isToolSelected(it, readonlyContext) }
|
|
|
|
/** Releases anything the toolset holds. There is nothing to release here. */
|
|
override fun close() {}
|
|
}
|
|
|
|
/**
|
|
* An agent using both an individual tool and a toolset. Kotlin keeps the two
|
|
* apart: individual tools go in `tools`, toolsets in `toolsets`.
|
|
*/
|
|
val calculatorAgent =
|
|
LlmAgent(
|
|
name = "calculator_agent",
|
|
model = Gemini(name = "gemini-flash-latest"),
|
|
instruction =
|
|
Instruction(
|
|
"You are a helpful calculator and greeter. Use greetUser for " +
|
|
"greetings. Use addNumbers to add and subtractNumbers to " +
|
|
"subtract. Announce the state of 'last_math_operation' if it is set.",
|
|
),
|
|
tools = GreetTools().generatedTools(),
|
|
toolsets = listOf(SimpleMathToolset()),
|
|
)
|
|
// --8<-- [end:init]
|
|
|
|
// --8<-- [start:filter]
|
|
// SimpleMathToolset, defined above, applies its optional ToolFilter inside
|
|
// getTools(). Passing one narrows what the same toolset exposes.
|
|
|
|
// Expose a fixed subset by name.
|
|
val addOnlyMath = SimpleMathToolset(ToolFilter.allowList("addNumbers"))
|
|
|
|
// Or decide per invocation. The predicate receives the ReadonlyContext, so the
|
|
// tool list can depend on session state, the user, or anything else on it.
|
|
val contextAwareMath =
|
|
SimpleMathToolset(
|
|
ToolFilter.Predicate { tool, context ->
|
|
tool.name == "addNumbers" || context?.state?.get("enable_advanced_math") == true
|
|
},
|
|
)
|
|
// --8<-- [end:filter]
|