Repository: cloudwego/eino

Issue:
hitl 多个mcp使用stream 报错
当我有两个tool，第一个不是InvokableTool第二个是（模拟一个故障场景巡检调用监控mcp发现异常，调用重启等动作mcp需要用户确认），这时候使用stream的话会报错，不用stream就没事。以下截图唯一的区别就是EnableStreaming

使用stream

![Image](https://github.com/user-attachments/assets/fa85976f-f5a3-4178-91c6-31de10f05b8c)

![Image](https://github.com/user-attachments/assets/2899e1ae-c24d-40cc-8aa1-d4a25a61c515)

不使用strean

![Image](https://github.com/user-attachments/assets/d3f85841-0f7f-40fc-8a55-163bef40e7e1)

![Image](https://github.com/user-attachments/assets/ed8a1e5d-5e91-4c5e-8aa3-fe9b2214f440)

报错的文本
2025/11/12 08:39:11 [NodeRunError] panic error: runtime error: invalid memory address or nil pointer dereference, 
stack: goroutine 57 [running]:
runtime/debug.Stack()
        D:/g/go/src/runtime/debug/stack.go:26 +0x5e
[github.com/cloudwego/eino/compose.(*taskManager).execute.func1()](http://github.com/cloudwego/eino/compose.(*taskManager).execute.func1())
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:278](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:278) +0x75
panic({0x1a9d900?, 0x2716520?})
        D:/g/go/src/runtime/panic.go:792 +0x132
[github.com/cloudwego/eino/compose.runToolCallTaskByStream](http://github.com/cloudwego/eino/compose.runToolCallTaskByStream)({0x1e082f8, 0xc0004b0090}, 0xc0000157a0, {0x27d37a0, 0x0, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:306](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:306) +0x6f
[github.com/cloudwego/eino/compose.parallelRunToolCall](http://github.com/cloudwego/eino/compose.parallelRunToolCall)({0x1e082f8, 0xc0004b0090}, 0x1cb82e8, {0xc0000157a0, 0x2, 0xc000421620?}, {0x27d37a0, 0x0, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:357](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:357) +0x21d
[github.com/cloudwego/eino/compose.(*ToolsNode).Stream](http://github.com/cloudwego/eino/compose.(*ToolsNode).Stream)(0xc000120940, {0x1e082f8, 0xc0004b0090}, 0xc0004a7520, {0x0, 0x0, 0xc000421dd0?})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:473](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/tool_node.go:473) +0x250
[github.com/cloudwego/eino/compose.newRunnablePacker[...].func22](http://github.com/cloudwego/eino/compose.newRunnablePacker[...].func22)(0xf87365?, {0x0?, 0x0?, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/utils.go:106](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/utils.go:106) +0x8b
[github.com/cloudwego/eino/compose.newRunnablePacker[...].func18](http://github.com/cloudwego/eino/compose.newRunnablePacker[...].func18)(0xc000421dd0, {0x0, 0x0?, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:303](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:303) +0x74
[github.com/cloudwego/eino/compose](http://github.com/cloudwego/eino/compose).(*runnablePacker[...]).Transform(...)
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:179](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:179)
[github.com/cloudwego/eino/compose](http://github.com/cloudwego/eino/compose).(*runnablePacker[...]).toComposableRunnable.func2({0x1e154e8, 0xc000421dd0}, {0x0, 0x0, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:143](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:143) +0xd9
[github.com/cloudwego/eino/compose.runnableTransform](http://github.com/cloudwego/eino/compose.runnableTransform)({0x1e082f8?, 0xc0004b0030?}, 0xc0003d9b60?, {0x1be9840?, 0xc000421dd0?}, {0x0?, 0x0?, 0x0?})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:106](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:106) +0xdd
[github.com/cloudwego/eino/compose.(*taskManager).execute](http://github.com/cloudwego/eino/compose.(*taskManager).execute)(0xc00035f090, 0xc000437a00)
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:285](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:285) +0xbd
[github.com/cloudwego/eino/compose.(*taskManager).submit](http://github.com/cloudwego/eino/compose.(*taskManager).submit)(0xc00035f090, {0xc000495440?, 0x1be9720?, 0xc0000f1050?})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:326](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_manager.go:326) +0x33c
[github.com/cloudwego/eino/compose.(*runner).run](http://github.com/cloudwego/eino/compose.(*runner).run)(0xc00044e5a0, {0x1e082f8, 0xc000421da0}, 0x1, {0x1be9720, 0xc0000f1050}, {0xc000015260, 0x2, 0x2})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:251](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:251) +0x12b6
[github.com/cloudwego/eino/compose.(*runner).transform](http://github.com/cloudwego/eino/compose.(*runner).transform)(0x1e047e0?, {0x1e082f8?, 0xc0000f0f00?}, {0x1e15490?, 0xc0000f1050?}, {0xc000015260?, 0x1c10660?, 0x1a77701?})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:91](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:91) +0x53
[github.com/cloudwego/eino/compose.(*graph).compile.(*runner).toComposableRunnable.func4](http://github.com/cloudwego/eino/compose.(*graph).compile.(*runner).toComposableRunnable.func4)({0x1e082f8, 0xc0000f0f00}, {0x1e15490, 0xc0000f1050}, {0xc000788080?, 0x20?, 0x1b3b9a0?})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:967](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/graph_run.go:967) +0x85
[github.com/cloudwego/eino/compose.toGenericRunnable[...].func2](http://github.com/cloudwego/eino/compose.toGenericRunnable[...].func2)(0xc0000f1050, {0xc000015180, 0x2, 0x0})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:428](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:428) +0x11b
[github.com/cloudwego/eino/compose.newRunnablePacker[...].func12](http://github.com/cloudwego/eino/compose.newRunnablePacker[...].func12)({0xc000494bc8, 0x1, 0x1}, {0xc000015180, 0x2, 0x2})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:230](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:230) +0xdd
[github.com/cloudwego/eino/compose](http://github.com/cloudwego/eino/compose).(*runnablePacker[...]).wrapRunnableCtx.func2({0xc000494bc8?, 0x1?, 0x1}, {0xc000015180, 0x2, 0x2})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:87](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:87) +0x94
[github.com/cloudwego/eino/compose](http://github.com/cloudwego/eino/compose).(*runnablePacker[...]).Stream(...)
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:167](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/compose/runnable.go:167)
[github.com/cloudwego/eino/adk.(*ChatModelAgent).buildRunFunc.func1.2](http://github.com/cloudwego/eino/adk.(*ChatModelAgent).buildRunFunc.func1.2)({0x1e082f8, 0xc000555800}, 0xc000099b60, 0xc000494b90, 0xc000099b40, {0xc00034e380, 0x1, 0x1})
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:678](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:678) +0x55a
[github.com/cloudwego/eino/adk.(*ChatModelAgent).Resume.func2()](http://github.com/cloudwego/eino/adk.(*ChatModelAgent).Resume.func2())
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:774](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:774) +0x16c
created by [github.com/cloudwego/eino/adk.(*ChatModelAgent).Resume](http://github.com/cloudwego/eino/adk.(*ChatModelAgent).Resume) in goroutine 1
        D:/g/versions/1.14/path/pkg/mod/[github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:763](http://github.com/cloudwego/eino@v0.5.12-0.20251106022159-dc9fd20f08c3/adk/chatmodel.go:763) +0x528

------------------------
node path: [ToolNode]
exit status 1



Hints from issue comments:
@shentongmartin: @yangzinan 帮忙看下，generate 和 stream 两次调用的时候，ToolsNode 中发生 panic 的那一行，task 的内容？我尝试复现没复现出来。@yangzinan: <img width="1398" height="174" alt="Image" src="https://github.com/user-attachments/assets/418ca0ea-e9f4-4a4c-8516-fc0d09c30b0a" />@shentongmartin: @yangzinan 不是这里，是 tool_node.go 306行报错时，局部变量 task 的内容，可以尝试 debug 看下。或者我们飞书上加联系人直接视频@yangzinan: runToolCallTaskByStream Task: {<nil> <nil> get_clusters {"page": "1", "pageSize": "100"} chatcmpl-tool-e6f8a9ee6af94bc4b77fb2aaeff4d2de true  0xc0008cf2c0 <nil>}
runToolCallTaskByStream Task: {0xc000510d40 0xc0004e3b30 BookTicket {"location": "东京", "passenger_name": "熊大", "passenger_phone_number": "13800138000"} chatcmpl-tool-9d6715aecf104557b8e7a5c8febb8795 false  <nil> <nil>}
@shentongmartin: @yangzinan 报错的 306 行，看下是 task 的 meta 字段为 nil 吗？如果是的话，看下是哪个 task@yangzinan: runToolCallTaskByStream Task.meta is nil: get_clusters@yangzinan: 第一个不用确认的工具@shentongmartin: @yangzinan runToolCallTaskByStream Task.meta is nil: get_clusters，这个 task  在 306 行，它的 executed 字段是 true 还是 false

Solution patch:
diff --git a/compose/tool_node.go b/compose/tool_node.go
index 52aee3e..ff73d39 100644
--- a/compose/tool_node.go
+++ b/compose/tool_node.go
@@ -352,7 +352,10 @@ func parallelRunToolCall(ctx context.Context,
 		}(ctx, &tasks[i], opts...)
 	}
 
-	run(ctx, &tasks[0], opts...)
+	if !tasks[0].executed {
+		run(ctx, &tasks[0], opts...)
+	}
+
 	wg.Wait()
 }
 



Test patch:
diff --git a/compose/tool_node_test.go b/compose/tool_node_test.go
index bd341eb..fae3160 100644
--- a/compose/tool_node_test.go
+++ b/compose/tool_node_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"strconv"
 	"strings"
 	"testing"
 
@@ -754,30 +755,30 @@ func TestToolRerun(t *testing.T) {
 
 	tc := []schema.ToolCall{
 		{
-			ID: "1",
+			ID: "3",
 			Function: schema.FunctionCall{
-				Name:      "tool1",
+				Name:      "tool3",
 				Arguments: "input",
 			},
 		},
 		{
-			ID: "2",
+			ID: "4",
 			Function: schema.FunctionCall{
-				Name:      "tool2",
+				Name:      "tool4",
 				Arguments: "input",
 			},
 		},
 		{
-			ID: "3",
+			ID: "1",
 			Function: schema.FunctionCall{
-				Name:      "tool3",
+				Name:      "tool1",
 				Arguments: "input",
 			},
 		},
 		{
-			ID: "4",
+			ID: "2",
 			Function: schema.FunctionCall{
-				Name:      "tool4",
+				Name:      "tool2",
 				Arguments: "input",
 			},
 		},
@@ -794,9 +795,18 @@ func TestToolRerun(t *testing.T) {
 		return state.In, nil
 	})))
 	assert.NoError(t, g.AddLambdaNode("lambda", InvokableLambda(func(ctx context.Context, input []*schema.Message) (output string, err error) {
-		sb := strings.Builder{}
+		contents := make([]string, len(input))
 		for _, m := range input {
-			sb.WriteString(m.Content)
+			callID := m.ToolCallID
+			callIDInt, err := strconv.Atoi(callID)
+			if err != nil {
+				return "", err
+			}
+			contents[callIDInt-1] = m.Content
+		}
+		sb := strings.Builder{}
+		for _, m := range contents {
+			sb.WriteString(m)
 		}
 		return sb.String(), nil
 	})))