Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#7,241 1 bình luận 0 reaction 1 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
35/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
ai, tooling

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Python
Star
21.6k
Fork
4k
Merge trung bình
13 giờ 49 phút
Pull request đã merge (30 ngày)
10

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của google/adk-python

Tất cả issue của google/adk-python

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.