Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Cache-stable progressive disclosure of tools (deferred tools + per-turn selection hook)

オープン
#7,241 コメント 1 件 リアクション 0 件 担当者 1 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
python
領域
ai, tooling

調査の方向性

Read tools/skill_toolset.py and models/gemini_context_cache_manager.py first, then trace tool resolution through functions.py and the flow code. Check how callbacks, plugins, argument validation, events, and cache history are handled today. Done means a design or implementation that keeps config.tools stable, persists deduplicated declarations, resolves deferred calls under real names, validates arguments, and supports per-turn selection with search_tools fallback.

索引モデルが issue の本文から書いたものです。

説明

needs review tools

Is your feature request related to a specific problem?

Yes. #4899 asked for tool search / progressive disclosure and was closed in favour of SkillToolset(additional_tools=...) together with adk_additional_tools in SKILL.md frontmatter. We use that mechanism heavily in production, across a dozen or so skills in several agents. It works for scoping tools, but three properties make it a poor fit for large toolsets on long, cached sessions.

Activating a skill changes config.tools, so the context cache misses.

SkillToolset.get_tools() adds the activated skills' tools to the request (_resolve_additional_tools_from_state, tools/skill_toolset.py, ADK 2.9.0).
GeminiContextCacheManager._generate_cache_fingerprint hashes config.tools along with the system instruction and the first N contents (models/gemini_context_cache_manager.py).
So every skill activation produces a new fingerprint. The session's explicit cache stops matching and has to be rebuilt partway through the conversation. Progressive disclosure and prefix caching are working against each other.
Activation only ever grows. The adk_activated_skill state list is append-only. After a few turns the agent carries the union of every tool it has touched, which is the "all tools up front" problem again, reached more slowly. Nothing lets you scope tools to a turn, or deactivate them.

Only the LLM can do the selecting. A tool becomes visible only when the model calls load_skill. There is no supported hook for choosing relevant tools per turn with something cheaper or more accurate, such as:

an embedding retriever;
a small classifier;
a structured-decision model;
deterministic rules.
The model also has to pick a skill bundle, not individual tools, so tool granularity is tied to how the skills are authored.

Describe the solution you'd like

First-class deferred tools: tools that are registered with the agent but never appear in config.tools. Their declarations are delivered through contents, and ADK executes them as if they were declared. Rough shape:

root_agent = LlmAgent(
name="concierge",
tools=[core_toolset], # always declared, cache-stable
deferred_tools=DeferredTools(
tools=[big_toolset_a, big_toolset_b], # registered, not declared
selector=my_selector, # optional per-turn hook (below)
search=True, # expose a built-in search_tools fallback
),
)
Requirements:

The declared tool surface stays fixed. config.tools holds only the always-on tools plus a fixed pair of built-ins:

search_tools(query) -> [declarations]
call_tool(name, args), or an equivalent resolution mechanism
The cache fingerprint therefore never changes because a tool was disclosed.

Disclosed declarations are persisted as session events, not request-only contents. This could be a function response from search_tools, or an event ADK writes when the selector runs. If a before_model hook injected them only into the outgoing request, they would sit inside the fingerprinted prefix but be missing from the stored history, so the cache would miss at every turn boundary. ADK should also deduplicate: a declaration already in history isn't re-sent.

Deferred tools run through the full tool pipeline under their real name. call_tool("get_order", {...}) must:

resolve to the real BaseTool;
run before_tool_callback / after_tool_callback and plugin tool callbacks with tool.name == "get_order";
emit events, traces and telemetry under the real name.
Guardrail plugins, analytics and auth wrappers that key on tool names must work unchanged. If they all see call_tool instead, the feature is unusable wherever there are guards.

Arguments are validated against the real schema. Deferred calls lose the model's constrained decoding, so ADK should validate args against the tool's declaration. On failure it should return a structured, model-readable error, not raise. The existing FunctionTool argument parsing is a natural place for this.

A per-turn selector hook. Signature something like:

async def selector(ctx: ReadonlyContext, candidates: list[ToolSummary]) -> list[str]
It runs once per invocation, before the first model call. It returns the tool names to disclose this turn, and ADK persists their declarations per requirement 2. The hook lets applications plug in a retriever, a classifier or rules. Without a selector, the model uses search_tools itself.

Tools the selector missed stay reachable. search_tools is always available as the fallback. This turns a missed selection into one extra round-trip instead of an unreachable tool.

Optional scoping. A per-turn or TTL option for disclosed tools, so their declarations and activation state don't pile up over a long session.

Describe alternatives you've considered

SkillToolset + adk_additional_tools: covered above. The cache misses on activation, activation never shrinks, and only the LLM can select.
BaseToolset.get_tools(readonly_context) / tool_filter returning a per-turn subset: this supports per-turn scoping. But the subset is config.tools, so the cache fingerprint changes every time the subset does. That's the same problem as the first alternative, only worse, because it can change every turn.
An application-level call_tool dispatcher plus schemas injected into contents: this is the design we'd have to build ourselves. It bypasses the per-tool callback and plugin chain, and it needs every name-keyed plugin rewritten to unwrap the dispatcher. That belongs in the framework.
A per-turn hint only ("likely relevant: X"), keeping every tool declared: cache-stable and cheap, but it doesn't reduce declaration tokens or selection distractors for large toolsets.
Additional context

Anthropic's API has tool search with deferred loading (defer_loading), linked from #4899. The linked Microsoft Agent Framework issue covers the same idea.
The request here is the ADK equivalent, with two additions: it must keep the Gemini context cache stable, and it must keep ADK's tool callbacks and plugins intact.
We're happy to contribute a design doc or PR. We'd especially like guidance on where name resolution should hook into functions.py / the flow, so callbacks see the real tool.

主要言語
Python
スター
21.6k
フォーク
4k
平均マージ
7時間 10分
マージ済み PR(30日)
7

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

google/adk-python のほかの issue

google/adk-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。