adachsoft/ai-agent-context-tool-compaction 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

adachsoft/ai-agent-context-tool-compaction

Composer 安装命令:

composer require adachsoft/ai-agent-context-tool-compaction

包简介

Conversation context strategy for adachsoft/ai-agent that compacts old tool call payloads to reduce token usage.

README 文档

README

Conversation context strategy for adachsoft/ai-agent that compacts old tool call payloads (arguments and results) in the conversation history to reduce LLM token usage.

  • PHP: >= 8.3
  • Runtime dependency: adachsoft/ai-agent (SPI: ConversationContextStrategyInterface)
  • Namespace: AdachSoft\\AiAgentContextToolCompaction\\

The strategy operates only on SPI DTOs from adachsoft/ai-agent and never interacts with the agent's internal domain model.

Installation

Install via Composer:

composer require adachsoft/ai-agent-context-tool-compaction

This package requires adachsoft/ai-agent (declared as a runtime dependency in composer.json).

Quick Start

1. Register the strategy in AiAgentBuilder

use AdachSoft\AiAgent\PublicApi\Builder\AiAgentBuilder;
use AdachSoft\AiAgentContextToolCompaction\ToolCompactionConversationContextStrategy;

$strategy = new ToolCompactionConversationContextStrategy();

$builder = (new AiAgentBuilder())
    // ... your ports and config ...
    ->withSpiConversationContextStrategy($strategy);

2. Enable the strategy in AgentConfigDto

The strategy is selected by its id on AgentConfigDto and configured via conversationContextStrategyParams.

use AdachSoft\AiAgent\PublicApi\Dto\AgentConfigDto;
use AdachSoft\AiAgentContextToolCompaction\ToolCompactionStrategyParams;

$config = new AgentConfigDto(
    // ... other required arguments ...
);

// Pseudocode: set these fields according to your AgentConfigDto API
$config->conversationContextStrategyId = ToolCompactionStrategyParams::STRATEGY_ID; // "tool_compaction"
$config->conversationContextStrategyParams = [
    'inputTrimBytes' => 100,
    'outputTrimBytes' => 100,
    'excludedTools' => [],
    'toolIdentifierFields' => [
        'read_file_content' => ['path', 'position', 'length'],
    ],
];

Note: the exact way of setting conversationContextStrategyId and conversationContextStrategyParams depends on the version of AgentConfigDto. The example above uses pseudo-code – adapt it to your public API.

3. Minimal configuration fixing the log bug

To ensure that consecutive read_file_content tool calls with the same path but different position/length are treated as different logical operations, configure:

$config->conversationContextStrategyParams = [
    'toolIdentifierFields' => [
        'read_file_content' => ['path', 'position', 'length'],
    ],
];

Without this configuration the strategy falls back to grouping only by toolName, which may compact older results for different segments of the same file.

Configuration

Parameters are passed as array<string, mixed> through AgentConfigDto::$conversationContextStrategyParams and resolved by ParamsResolver. Invalid types are handled fail-open – the strategy uses safe defaults and never throws.

Supported keys (all optional):

  • inputTrimBytes (int, default: 100)
    • Maximum payload size in bytes for individual tool argument fields before they are compacted.
  • outputTrimBytes (int, default: 100)
    • Maximum payload size in bytes for individual tool result fields before they are compacted.
  • excludedTools (string[], default: [])
    • List of toolName values for which no compaction is performed at all (both input and output remain untouched).
  • toolIdentifierFields (array<string, string[]>, default: [])
    • Map of toolName to the list of top-level argument keys that together act as a logical identifier.
    • These fields:
      • participate in deduplication key creation,
      • are never trimmed, even if they exceed the configured thresholds.

Identifier fallback rule

If a tool is not present in toolIdentifierFields, it is treated as if it had no identifier arguments. In this case, the deduplication key is based only on the toolName (see the algorithm section below). This may cause older calls of the same tool with different arguments to be compacted together.

Make sure to configure toolIdentifierFields for tools where arguments define "what" is being processed (e.g. path, position, length for read_file_content).

Algorithm Overview

The strategy implements the algorithm described in .tasks/task002-plan-v1.md:

  • Input: full SPI conversation history as a ConversationMessageDtoCollection.
  • Output: new ConversationMessageDtoCollection with the same messages in the same order, but with selected tool payloads compacted.
  • The strategy is a pure function over SPI DTOs:
    • it never mutates incoming DTO instances,
    • it never throws exceptions (fail-open behaviour).

Data model (SPI DTOs)

Relevant DTOs from adachsoft/ai-agent:

  • ConversationMessageDto:
    • role (system | user | assistant | tool),
    • content (?string),
    • toolCalls (ToolCallDtoCollection),
    • toolResults (ToolCallResultDtoCollection).
  • ToolCallDto:
    • id (tool call id),
    • toolName,
    • arguments (array<string, mixed>).
  • ToolCallResultDto:
    • id (tool call id – correlates with ToolCallDto::$id),
    • toolName,
    • result (array<string, mixed>).

In many setups role='tool' messages also have content set to a json_encode($result) payload. The strategy ensures that once a tool result is compacted, the human-visible content does not keep a large JSON payload.

Deduplication key (IdentifierGroupKey)

For each tool call, the strategy computes a group key:

  1. If toolIdentifierFields[toolName] is configured and non-empty:
    • it builds a deterministic string from (fieldName => value) pairs for the configured fields,
    • missing values are represented as null,
    • field order is preserved as in the configuration (no sorting).
  2. If there is no configuration for the tool:
    • the identifier key is a constant "__tool_only__".

The final IdentifierGroupKey is:

{toolName}|{identifierKey}

Protect the latest occurrence (input + output)

Business rule: for each IdentifierGroupKey, the latest occurrence of a logical tool call (input and output) must remain fully intact.

To achieve this, the strategy:

  1. Builds an index of all tool calls:

    $toolCallIndex[toolCallId] = [
        'toolName' => string,
        'arguments' => array<string, mixed>,
        'groupKey' => string,
    ];
    
  2. Iterates over messages from newest to oldest and keeps:

    • seenGroups: set<string> – which group keys were already seen from the end of the conversation,
    • protectedToolCallIds: set<string> – ids of tool calls that belong to the most recent occurrence of a group.

During this reverse scan:

  • When processing tool results:
    • the first (latest) result for each groupKey is left untouched and its id is added to protectedToolCallIds;
    • subsequent (older) results in the same group are compacted.
  • When processing tool calls:
    • any call whose id is in protectedToolCallIds is always left untouched;
    • otherwise, the first (latest) call for each groupKey is preserved and its id is added to protectedToolCallIds;
    • subsequent (older) calls in the same group are compacted.

Compaction behaviour

Compaction is delegated to PayloadCompactor, which operates on plain arrays and uses PayloadSizer for size calculations.

PayloadSizer

  • stringstrlen($value)
  • null / bool / int / floatstrlen((string) $value)
  • arraystrlen(json_encode($value)) (without JSON_THROW_ON_ERROR)
  • unsupported types (objects, resources, etc.) → size 0 (fail-open, no trimming)

compactInput(arguments, thresholdBytes, identifierFields)

  • Iterates over top-level keys in arguments.
  • If key is in identifierFields → the field is never trimmed.
  • Otherwise, if size(value) > thresholdBytes (and size > 0):

    • replaces the value with the placeholder string "[omitted]",
    • records metadata under the meta key '_tool_compaction':

      '_tool_compaction' => [
          'thresholdBytes' => $thresholdBytes,
          'omittedFields' => [
              $key => [
                  'bytes' => $calculatedSize,
                  'sha256' => hash('sha256', json_encode($originalValue)),
              ],
              // ...
          ],
      ]
      
  • Meta is added only if at least one field was actually omitted.

compactOutput(result, thresholdBytes)

  • Works analogously to compactInput() but without identifier fields – all keys are eligible for trimming.

Message content placeholder for tool results

If at least one tool result in a role='tool' message was compacted, the strategy also replaces the human-visible content with a short placeholder:

[tool_compaction] Tool result compacted for tool={toolName}, callId={toolCallId}. Large fields omitted.

The placeholder never includes any original tool data.

Fail-open and safety

  • Invalid parameter types → safe defaults (100/100/[]/[]).
  • JSON encoding failures → field left unchanged (no trimming).
  • The strategy never throws; if anything unexpected happens, the payload is left as-is.

Behaviour Examples

The following examples mirror the behaviour described in the original design specification.

Example A – read_file_content with identifiers (path, position, length)

Configuration:

[
    'inputTrimBytes' => 100,
    'outputTrimBytes' => 100,
    'toolIdentifierFields' => [
        'read_file_content' => ['path', 'position', 'length'],
    ],
]

Timeline (chronological):

  1. Assistant calls read_file_content with {path: "A.php", position: 0, length: 6000} (call_1).
  2. Tool returns a very large content for call_1.
  3. Assistant calls read_file_content again with the same arguments (call_2).
  4. Tool returns another large content for call_2.

Result after transform:

  • call_2 (latest group member) remains fully intact (input and output).
  • call_1 (older in the same group):

    • result.content is replaced with "[omitted]",
    • meta _tool_compaction is added to the result array,
    • the role='tool' message content becomes:

      [tool_compaction] Tool result compacted for tool=read_file_content, callId=call_1. Large fields omitted.
      

Example B – same path, different position/length (no cross-group dedup)

With the configuration from Example A, two calls:

  • call_A: {path: "A.php", position: 0, length: 6000}
  • call_B: {path: "A.php", position: 6000, length: 4000}

produce different identifier keys and thus different groups. Each (group, latest) pair is handled independently – no deduplication across (position, length).

Example C – write_file_content with large input, small output

Configuration:

[
    'inputTrimBytes' => 100,
    'outputTrimBytes' => 100,
    'toolIdentifierFields' => [
        'write_file_content' => ['path'],
    ],
]

Timeline:

  1. Assistant: write_file_content (call_1) with {path: "A.php", content: "<VERY_LONG>"}.
  2. Tool: small result {status: "ok"} for call_1.
  3. Assistant: write_file_content (call_2) with {path: "A.php", content: "<VERY_LONG_V2>"}.
  4. Tool: small result {status: "ok"} for call_2.

Result:

  • call_2 remains full (input and output).
  • call_1 (older in the same group):
    • arguments.path is preserved (identifier),
    • arguments.content is replaced with "[omitted]" + meta _tool_compaction.
    • Result is small enough → unchanged.

Example D – no toolIdentifierFields configuration

If a tool (e.g. list_files) is not present in toolIdentifierFields, then:

  • all its calls share the same identifier key "__tool_only__",
  • all occurrences of this tool across the conversation belong to a single deduplication group.

This means that older calls may be compacted even if their arguments differ. For such tools you should explicitly configure toolIdentifierFields.

License

MIT. See LICENSE file and composer.json.

adachsoft/ai-agent-context-tool-compaction 适用场景与选型建议

adachsoft/ai-agent-context-tool-compaction 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 11 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 01 月 31 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「Context」 「tool」 「conversation」 「ai-agent」 「adachsoft」 「token-optimization」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 adachsoft/ai-agent-context-tool-compaction 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 adachsoft/ai-agent-context-tool-compaction 我们能提供哪些服务?
定制开发 / 二次开发

基于 adachsoft/ai-agent-context-tool-compaction 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 11
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 37
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-31