cloudwego / cloudwego/eino

[Feature] compose: expose tool call batch index/size to tools (GetToolCallInfo), not only GetToolCallID

Open
#1,289 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
13k
Forks
1.1k
Avg merge
4h 6m
Merged PRs (30d)
41

Description

## Problem

The only per-call information a tool can read from its `context.Context` is the model-generated call ID: `compose.GetToolCallID` (`compose/tool_node.go:1297-1310`, v0.9.19), backed by `toolCallInfo` whose single field is `toolCallID` (`:1289-1291`).

`ToolsNode` does know more at the moment it builds the context: `genToolCallTasks` iterates `input.ToolCalls[i]` (`:791-792`) and keeps the batch slot `i` in `tasks[i]`, and `runToolCallTaskByInvoke` / `runToolCallTaskByStream` set the context per task (`:902`, `:941`). The slot index and the batch size are just not exposed.

## Why it matters

A host SDK that needs a **structural, model-independent identity** for a tool invocation (idempotency keys for side-effecting tools that must survive checkpoint/resume, correlating parallel calls to their position in the assistant turn, batch-level barriers) cannot derive it from the call ID: the ID is minted by the model/gateway, may be empty or duplicated within one batch, and can change when the same turn is regenerated. "The i-th call of this assistant message" is stable across all of those.

## Proposed API (additive)

```go
// ToolCallInfo describes the tool call currently being executed by a ToolsNode.
type ToolCallInfo struct {
CallID string // same value GetToolCallID returns today
Index int // position in the assistant message's ToolCalls slice
Total int // len(ToolCalls) for this batch
}

// GetToolCallInfo returns the current tool call info, and false when the
// context is not a ToolsNode execution context.
func GetToolCallInfo(ctx context.Context) (ToolCallInfo, bool)
```

`GetToolCallID` keeps its signature and semantics. Population is local to `genToolCallTasks` (store `i` / `n` on `toolCallTask`) and the two `setToolCallInfo` sites. Exposing the parent assistant message's ID or the `ToolsNode`'s iteration number would also be welcome but is not required for this request.

## Workaround today & its cost

Two options exist, both worse than an official accessor:

1. Read the graph state from inside the tool via `compose.ProcessState[*adk.State]` and scan `state.Messages` for the assistant message that contains the current call ID. `adk.State` is documented as *"exported only for checkpoint backward compatibility. Do not use it directly."* (`adk/react.go:59-63`), so this ties the SDK to a symbol scheduled for retirement, and it still needs a call ID to search by.
2. Build the mapping call-ID → (turn, index) in an `AfterModelRewriteState` hook and rebuild it on resume via `WithHistoryModifier`. This works but re-derives, from the outside, a value the node already holds, and again collapses when two calls in one batch share an ID.

## Minimal reproduction (v0.9.19)

```go
// inside a tool's InvokableRun:
id := compose.GetToolCallID(ctx) // "" or duplicated when the model misbehaves
// no way to obtain: which slot of the batch am I, and how large is the batch
```

Feed an assistant message with two `ToolCalls` that share `ID: "call_1"` into a `ToolsNode`; both tools see the same `GetToolCallID` result and have no other handle to tell themselves apart.

Contributor guide

Open the contributing guide

Research direction

Start in compose/tool_node.go by reading GetToolCallID, toolCallInfo, genToolCallTasks, and the runToolCallTaskByInvoke and runToolCallTaskByStream context setup sites. Trace how the task index and ToolCalls length flow into each context; done means a public GetToolCallInfo accessor exposes CallID, Index, and Total while preserving GetToolCallID behavior and distinguishing duplicate call IDs.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
ai, backend-api-design
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.