agentscope-ai / agentscope-ai/agentscope-java

[Feature] 支持缓存稳定的渐进式工具披露,避免 ToolGroup 变化导致 Prompt Cache 前缀整体失配

Đang mở
#2,784 1 bình luận 2 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Java
Star
5.6k
Fork
1.3k
Merge trung bình
4 ngày 12 giờ
Pull request đã merge (30 ngày)
77

Mô tả

## Summary

AgentScope 的 ToolGroup / reset_tools 支持按需激活工具,只向模型披露当前工具,从而降低工具 schema 数量。

但在 DashScope 显式 Context Cache 场景下,工具组一旦发生变化,发送给模型的完整 tools 数组也会变化。DashScope 会把完整工具定义计入 system 前缀,因此原有的 system
prompt、历史消息等缓存前缀都无法继续命中。

希望 AgentScope 提供一种可选的“Cache-Stable Tool Exposure”模式:既保留渐进披露和运行时权限约束,又避免动态工具变化破坏稳定的 system/history 缓存。

## Environment

- AgentScope Java:2.0.0
- Java:17
- Model provider:DashScope
- Model:qwen3.7-max
- Agent:ReActAgent
- Tool management:ToolGroup 渐进披露
- Context Cache:cache_control: {"type": "ephemeral"}
- Cache marker:
- 第一条 system 消息
- 每次请求最后一条文本消息

实际发送的 content-block 级 cache_control 已确认正确。

## Background

初始模型可见工具:

A = [
resolve,
reset_tools,
todo_write,
wait_async_results,
get_approval_progress
]

激活订单工具组后:

A+B = [
resolve,
reset_tools,
todo_write,
wait_async_results,
get_approval_progress,
query_orders,
get_order_detail,
count_orders
]

历史消息、系统提示词本身没有改变,只有工具面从 A 变成了 A+B。

但 DashScope 对缓存前缀的实际计算相当于:

旧缓存块:
K1 = system + 完整 tools[A]
K2 = system + 完整 tools[A] + history

新请求:
K1' = system + 完整 tools[A+B]
K2' = system + 完整 tools[A+B] + history

因为 tools[A] != tools[A+B],所以 K1 和 K2 都无法命中。

DashScope 官方文档也明确说明:工具定义会纳入 system 消息的缓存计算,并且工具定义不能单独设置缓存 marker。DashScope Context Cache
(https://help.aliyun.com/en/model-studio/context-cache)

AgentScope 官方 ToolGroup 设计则会在工具组激活后修改模型可见工具集合。AgentScope ToolGroup 文档 (https://java.agentscope.io/v2/en/docs/building-blocks/tool.html)

## Minimal Reproduction

### 第一次模型调用

{
"tools": ["base_tool_A"],
"messages": [
{
"role": "system",
"content": [{
"type": "text",
"text": "stable system prompt",
"cache_control": {"type": "ephemeral"}
}]
},
{
"role": "user",
"content": [{
"type": "text",
"text": "query an order",
"cache_control": {"type": "ephemeral"}
}]
}
]
}

模型调用 reset_tools,激活 order group。

### 第二次 ReAct 模型调用

{
"tools": [
"base_tool_A",
"query_orders_B",
"get_order_detail_B"
],
"messages": [
{"role": "system", "content": "...same system prompt..."},
{"role": "user", "content": "...same user message..."},
{"role": "assistant", "tool_calls": ["reset_tools"]},
{
"role": "tool",
"content": [{
"type": "text",
"text": "order group activated",
"cache_control": {"type": "ephemeral"}
}]
}
]
}

即使 system 和已有历史消息完全一致,第二次调用也无法命中第一次调用创建的缓存前缀。

### 对照实验

A → A :可以命中
A → A+B :缓存 miss
A+B → A+B :可以命中新的 A+B 缓存

## Actual Behavior

我们的真实调用采样:

模型调用:54 次
缓存命中:47 次
缓存未命中:7 次

其中:
- 1 次服务冷启动
- 6 次工具面变化

相同工具面重复调用可以命中;重复设置为相同工具列表也不会破坏缓存。

这说明 miss 与工具面变化具有明确对应关系。

## Expected Behavior

希望存在一种可选模式,使工具组激活时:

1. system prompt 和已有历史消息仍能复用旧缓存。
2. 只有新披露的工具信息和本轮新增消息需要处理。
3. 未激活工具在运行时仍然不可调用。
4. 保持当前动态 native tools 行为作为默认模式,避免兼容性变化。

## Capability Boundary

在当前 DashScope 协议下,以下三个条件无法同时满足:

1. 未激活工具完全不出现在顶层 tools 中。
2. 激活后把真实工具 schema 加入顶层 tools。
3. 工具变化后继续命中旧 system/history 前缀缓存。

因为 DashScope 会把完整 tools 计入 system 缓存前缀,而且不支持在工具数组内部设置独立缓存断点。

因此,仅调整 cache_control marker 位置无法解决该问题,需要改变工具暴露方式,或者由模型供应商提供分段缓存能力。

## Suggested Solution

### 方案一:稳定 Meta Tool + 动态发现/调用,推荐

模型始终只看到固定的少量 meta tools:

resolve_tool_groups
activate_tool_groups
invoke_tool
todo_write
wait_async_results

激活工具组时,不修改顶层 tools,而是通过 tool result 返回新工具的名称、说明和参数 schema:

{
"activatedGroups": ["order"],
"availableTools": [
{
"name": "query_orders",
"description": "Query orders by conditions",
"parameters": {
"type": "object",
"properties": {}
}
}
]
}

模型通过固定的 invoke_tool 调用真实工具:

{
"name": "invoke_tool",
"arguments": {
"toolName": "query_orders",
"toolArguments": {
"orderNo": "PO123"
}
}
}

AgentScope 运行时负责:

- 检查工具所属组是否已激活
- 按真实 ToolSchema 校验参数
- 调用对应的 AgentTool
- 返回结构化工具结果

这样顶层工具 schema 永远稳定:

cache prefix = system + 固定 meta tools + history

工具披露发生时,只在消息尾部追加新的工具说明,不会破坏前面的 system/history 缓存。

代价:

- 失去模型供应商对每个业务工具的原生参数约束
- 需要 AgentScope 在执行阶段完成二次 schema 校验
- 可能增加一次发现/激活调用

### 方案二:固定完整工具面 + 运行时权限控制

始终向模型发送全部工具 schema,ToolGroup 只控制工具是否允许执行。

优点:

- 实现简单
- Prompt Cache 最稳定
- 保留原生 function calling schema

缺点:

- 不再是真正的模型侧渐进披露
- 首轮工具 token 增加
- 工具较多时可能降低工具选择准确率

适合作为工具数量不多时的短期方案。

### 方案三:稳定主 Agent + 专业子 Agent

主 Agent 始终只暴露固定的领域代理工具:

order_agent
logistics_agent
approval_agent

每个专业 Agent 内部使用固定的业务工具集合。这样:

- 主 Agent 的 system/history/tool surface 始终稳定
- 各专业 Agent 分别形成自己的缓存前缀
- 保留业务工具的原生 schema

缺点是多一次 Agent 调用和上下文交接。AgentScope 已有 Agent as Tool (https://java.agentscope.io/en/task/agent-as-tool.html),但目前属于实验能力。

## Proposed API

可以考虑增加工具暴露策略:

public enum ToolExposureMode {
DYNAMIC_NATIVE_SCHEMA, // 当前行为
STABLE_SUPERSET_RUNTIME_GATED, // 全量稳定 schema,运行时控制权限
META_DISCOVERY_AND_DISPATCH // 固定 meta tool,动态发现和转发
}

示例:

ReActAgent.builder()
.toolkit(toolkit)
.toolExposureMode(ToolExposureMode.META_DISCOVERY_AND_DISPATCH)
.build();

也可以先提供更低层的扩展点,让应用自行实现:

- 模型可见工具 schema provider
- 运行时可执行工具 whitelist
- invokeTool(name, arguments) 统一分发入口
- 稳定的 ToolSchema 序列化与 fingerprint
- 模型调用前后的 tool-surface-change 事件

## Questions

1. AgentScope 当前是否已经存在“模型可见工具保持稳定,但执行权限随 ToolGroup 改变”的模式?
2. 是否有官方扩展点可以实现固定 invoke_tool,并复用 Toolkit 已有的参数校验和执行逻辑?
3. AgentScope 是否愿意增加 ToolExposureMode 一类的缓存友好模式?
4. 对 DashScope 场景,官方更推荐稳定 meta tool、完整工具面,还是 Agent-as-Tool?
5. 是否可以增加 tool-surface fingerprint 与 cached_tokens 观测,帮助识别工具 schema 顺序或结构变化造成的意外 miss?

## Acceptance Criteria

在 cache-stable 模式下:

- 激活 ToolGroup 前后,发送给模型的顶层 tools JSON 保持完全一致。
- 未激活工具不能被 invoke_tool 执行。
- 激活后能够根据真实 ToolSchema 校验参数并调用工具。
- 使用至少 1024 token 的稳定前缀,在 5 分钟内执行:

A → activate B → invoke B

激活后的模型调用仍能看到 cached_tokens > 0,且命中部分至少覆盖原 system prompt 和历史消息。

- 当前 DYNAMIC_NATIVE_SCHEMA 行为保持兼容。

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

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

Đánh giá

Issue này chưa được đánh giá.

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.