Repository: cloudwego/eino

Issue:
error: failed to save checkpoint
在`v0.7.0-alpha.2`版本将`eino-example/adk/human-in-the-loop/1_approval`中的 示例Agent 改为类似`eino-example/adk/multiagent/plan-execute-replan` 的多Agent，多次运行都报错：
```go
failed to save checkpoint: failed to encode checkpoint: gob: type not registered for interface: []*schema.Message
```

Discussion:
@shentongmartin: 可以先在 example 里面加上 schema.Register\[[]*schema.Message\]() 试一下@cuixiaojun001: 注册上之后有效，除此之外，因为我使用了planexecute包，还存在报错如下：
```go
failed to save checkpoint: failed to encode checkpoint: gob: type not registered for interface: planexecute.defaultPlan
```
这似乎无法再在example中注册了，由此可能还有其它multiagent类型无法save checkpoint
@cuixiaojun001: 目前尝试了adk/prebuilt目录下的executeplan和supervisor包，仅发现Plan-Execute Agent需要单独注册defaultPlan

Solution patch:
diff --git a/adk/prebuilt/deep/deep.go b/adk/prebuilt/deep/deep.go
index 7901c76..c7ce5d2 100644
--- a/adk/prebuilt/deep/deep.go
+++ b/adk/prebuilt/deep/deep.go
@@ -66,22 +66,24 @@ func New(ctx context.Context, cfg *Config) (adk.Agent, error) {
 
 	middlewares = append([]adk.AgentMiddleware{{AdditionalInstruction: baseAgentPrompt}}, middlewares...)
 
-	tt, err := newTaskToolMiddleware(
-		ctx,
-		cfg.TaskToolDescriptionGenerator,
-		cfg.SubAgents,
-
-		cfg.WithoutGeneralSubAgent,
-		cfg.ChatModel,
-		cfg.Instruction,
-		cfg.ToolsConfig,
-		cfg.MaxIteration,
-		middlewares, // append(cfg.Middlewares, middlewares...),
-	)
-	if err != nil {
-		return nil, fmt.Errorf("failed to new task tool: %w", err)
+	if !cfg.WithoutGeneralSubAgent || len(cfg.SubAgents) > 0 {
+		tt, err := newTaskToolMiddleware(
+			ctx,
+			cfg.TaskToolDescriptionGenerator,
+			cfg.SubAgents,
+
+			cfg.WithoutGeneralSubAgent,
+			cfg.ChatModel,
+			cfg.Instruction,
+			cfg.ToolsConfig,
+			cfg.MaxIteration,
+			middlewares, // append(cfg.Middlewares, middlewares...),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("failed to new task tool: %w", err)
+		}
+		middlewares = append(middlewares, tt)
 	}
-	middlewares = append(middlewares, tt)
 
 	return adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
 		Name:          cfg.Name,
diff --git a/adk/prebuilt/planexecute/plan_execute.go b/adk/prebuilt/planexecute/plan_execute.go
index c802769..912f736 100644
--- a/adk/prebuilt/planexecute/plan_execute.go
+++ b/adk/prebuilt/planexecute/plan_execute.go
@@ -34,6 +34,10 @@ import (
 	"github.com/cloudwego/eino/schema"
 )
 
+func init() {
+	schema.RegisterName[*defaultPlan]("_eino_adk_plan_execute_default_plan")
+}
+
 // Plan represents an execution plan with a sequence of actionable steps.
 // It supports JSON serialization and deserialization while providing access to the first step.
 type Plan interface {
diff --git a/schema/message.go b/schema/message.go
index f3ad5a0..c9a3e3d 100644
--- a/schema/message.go
+++ b/schema/message.go
@@ -145,8 +145,9 @@ const (
 
 // MessagePartCommon represents the common abstract components for input and output of multi-modal types.
 type MessagePartCommon struct {
-	// URL can either be a traditional URL or a special URL conforming to RFC-2397 (https://www.rfc-editor.org/rfc/rfc2397).
-	// double check with model implementations for detailed instructions on how to use this.
+	// URL is primarily used for HTTP or HTTPS access links.
+	// For data in the format 'data:[<mediatype>][;base64],<data>' (the 'data' URL Schema of RFC-2397 (https://www.rfc-editor.org/rfc/rfc2397)),
+	// it is recommended to use Base64Data and MIMEType fields separately instead.
 	URL *string `json:"url,omitempty"`
 
 	// Base64Data represents the binary data in Base64 encoded string format.
@@ -240,6 +241,7 @@ type MessageOutputPart struct {
 }
 
 // Deprecated: This struct is deprecated as the MultiContent field is deprecated.
+// For the image input part of the model, use MessageInputImage, For the image output part of the model, use MessageOutputImage
 // ChatMessageImageURL is used to represent an image part in a chat message.
 // Choose either URL or URI.
 // If your model implementation supports it, URL could be used to embed inline image data as defined in RFC-2397.
@@ -275,6 +277,7 @@ const (
 )
 
 // Deprecated: This struct is deprecated as the MultiContent field is deprecated.
+// For the audio input part of the model, use MessageInputAudio, For the audio output part of the model, use MessageOutputAudio
 // ChatMessageAudioURL is used to represent an audio part in a chat message.
 // Choose either URL or URI.
 // If your model implementation supports it, URL could be used to embed inline audio data as defined in RFC-2397.
@@ -291,6 +294,7 @@ type ChatMessageAudioURL struct {
 }
 
 // Deprecated: This struct is deprecated as the MultiContent field is deprecated.
+// For the video input part of the model, use MessageInputVideo, For the video output part of the model, use MessageOutputVideo
 // ChatMessageVideoURL is used to represent an video part in a chat message.
 // Choose either URL or URI.
 // If your model implementation supports it, URL could be used to embed inline video data as defined in RFC-2397.
@@ -307,6 +311,7 @@ type ChatMessageVideoURL struct {
 }
 
 // Deprecated: This struct is deprecated as the MultiContent field is deprecated.
+// For the file input part of the model, use MessageInputFile
 // ChatMessageFileURL is used to represent an file part in a chat message.
 // Choose either URL or URI.
 type ChatMessageFileURL struct {
@@ -323,6 +328,7 @@ type ChatMessageFileURL struct {
 }
 
 // Deprecated: This struct is deprecated as the MultiContent field is deprecated.
+// For the input of the model, use MessageInputPart, For the output of the model, use MessageOutputPart
 // ChatMessagePart is the part in a chat message.
 type ChatMessagePart struct {
 	// Type is the type of the part, eg. "text", "image_url", "audio_url", "video_url", "file_url".
diff --git a/schema/serialization.go b/schema/serialization.go
index 783de08..7a719b0 100644
--- a/schema/serialization.go
+++ b/schema/serialization.go
@@ -26,6 +26,7 @@ import (
 
 func init() {
 	RegisterName[Message]("_eino_message")
+	RegisterName[[]*Message]("_eino_message_slice")
 	RegisterName[Document]("_eino_document")
 	RegisterName[RoleType]("_eino_role_type")
 	RegisterName[ToolCall]("_eino_tool_call")



Test patch:
diff --git a/adk/interrupt_test.go b/adk/interrupt_test.go
index 9051680..f51c672 100644
--- a/adk/interrupt_test.go
+++ b/adk/interrupt_test.go
@@ -72,7 +72,7 @@ func TestSaveAgentEventWrapper(t *testing.T) {
 func TestSimpleInterrupt(t *testing.T) {
 	data := "hello world"
 	agent := &myAgent{
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				Output: &AgentOutput{
@@ -94,7 +94,7 @@ func TestSimpleInterrupt(t *testing.T) {
 			generator.Close()
 			return iter
 		},
-		resumer: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		resumeFn: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			assert.NotNil(t, info)
 			assert.True(t, info.EnableStreaming)
 			assert.Equal(t, data, info.Data)
@@ -119,15 +119,17 @@ func TestSimpleInterrupt(t *testing.T) {
 	_, ok = iter.Next()
 	assert.False(t, ok)
 
-	_, err := runner.Resume(ctx, "1")
+	iter, err := runner.Resume(ctx, "1")
 	assert.NoError(t, err)
+	_, ok = iter.Next()
+	assert.False(t, ok)
 }
 
 func TestMultiAgentInterrupt(t *testing.T) {
 	ctx := context.Background()
 	sa1 := &myAgent{
 		name: "sa1",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa1",
@@ -143,7 +145,7 @@ func TestMultiAgentInterrupt(t *testing.T) {
 	}
 	sa2 := &myAgent{
 		name: "sa2",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa2",
@@ -156,7 +158,7 @@ func TestMultiAgentInterrupt(t *testing.T) {
 			generator.Close()
 			return iter
 		},
-		resumer: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		resumeFn: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			assert.NotNil(t, info)
 			assert.Equal(t, info.Data, "hello world")
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
@@ -199,7 +201,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 	ctx := context.Background()
 	sa1 := &myAgent{
 		name: "sa1",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa1",
@@ -212,7 +214,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 			generator.Close()
 			return iter
 		},
-		resumer: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		resumeFn: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			assert.Equal(t, info.Data, "sa1 interrupt data")
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Close()
@@ -221,7 +223,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 	} // interrupt once
 	sa2 := &myAgent{
 		name: "sa2",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa2",
@@ -234,7 +236,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 			generator.Close()
 			return iter
 		},
-		resumer: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		resumeFn: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			assert.Equal(t, info.Data, "sa2 interrupt data")
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Close()
@@ -243,7 +245,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 	} // interrupt once
 	sa3 := &myAgent{
 		name: "sa3",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa3",
@@ -259,7 +261,7 @@ func TestWorkflowInterrupt(t *testing.T) {
 	} // won't interrupt
 	sa4 := &myAgent{
 		name: "sa4",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa4",
@@ -612,7 +614,7 @@ func TestChatModelInterrupt(t *testing.T) {
 
 func TestChatModelAgentToolInterrupt(t *testing.T) {
 	sa := &myAgent{
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				Action: &AgentAction{Interrupted: &InterruptInfo{
@@ -622,7 +624,7 @@ func TestChatModelAgentToolInterrupt(t *testing.T) {
 			generator.Close()
 			return iter
 		},
-		resumer: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		resumeFn: func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			assert.NotNil(t, info)
 			assert.False(t, info.EnableStreaming)
 			assert.Equal(t, "hello world", info.Data)
@@ -749,9 +751,9 @@ func withValue(value string) AgentRunOption {
 }
 
 type myAgent struct {
-	name    string
-	runner  func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
-	resumer func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent]
+	name     string
+	runFn    func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
+	resumeFn func(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent]
 }
 
 func (m *myAgent) Name(ctx context.Context) string {
@@ -766,11 +768,11 @@ func (m *myAgent) Description(ctx context.Context) string {
 }
 
 func (m *myAgent) Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
-	return m.runner(ctx, input, options...)
+	return m.runFn(ctx, input, options...)
 }
 
 func (m *myAgent) Resume(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
-	return m.resumer(ctx, info, opts...)
+	return m.resumeFn(ctx, info, opts...)
 }
 
 type myModel struct {
@@ -825,7 +827,7 @@ func TestWorkflowInterruptInvalidDataType(t *testing.T) {
 	// Create a simple workflow agent
 	sa1 := &myAgent{
 		name: "sa1",
-		runner: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			iter, generator := NewAsyncIteratorPair[*AgentEvent]()
 			generator.Send(&AgentEvent{
 				AgentName: "sa1",
diff --git a/adk/workflow_test.go b/adk/workflow_test.go
index 9a59051..859df9f 100644
--- a/adk/workflow_test.go
+++ b/adk/workflow_test.go
@@ -652,7 +652,7 @@ func TestWorkflowAgentResumeInvalidDataType(t *testing.T) {
 func TestFilterOptions(t *testing.T) {
 	a1 := &myAgent{
 		name: "Agent1",
-		runner: func(ctx context.Context, input *AgentInput, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		runFn: func(ctx context.Context, input *AgentInput, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			o := GetImplSpecificOptions[myAgentOptions](nil, opts...)
 			assert.Equal(t, "Agent1", o.value)
 			iter, gen := NewAsyncIteratorPair[*AgentEvent]()
@@ -661,8 +661,8 @@ func TestFilterOptions(t *testing.T) {
 		},
 	}
 	a2 := &myAgent{
-		name: "Agent1",
-		runner: func(ctx context.Context, input *AgentInput, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
+		name: "Agent2",
+		runFn: func(ctx context.Context, input *AgentInput, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] {
 			o := GetImplSpecificOptions[myAgentOptions](nil, opts...)
 			assert.Equal(t, "Agent2", o.value)
 			iter, gen := NewAsyncIteratorPair[*AgentEvent]()
@@ -676,13 +676,16 @@ func TestFilterOptions(t *testing.T) {
 		SubAgents: []Agent{a1, a2},
 	})
 	assert.NoError(t, err)
-	seqAgent.Run(ctx, &AgentInput{}, withValue("Agent1").DesignateAgent("Agent1"), withValue("Agent2").DesignateAgent("Agent2"))
+	iter := seqAgent.Run(ctx, &AgentInput{}, withValue("Agent1").DesignateAgent("Agent1"), withValue("Agent2").DesignateAgent("Agent2"))
+	_, ok := iter.Next()
+	assert.False(t, ok)
 
 	// parallel
 	parAgent, err := NewParallelAgent(ctx, &ParallelAgentConfig{
 		SubAgents: []Agent{a1, a2},
 	})
 	assert.NoError(t, err)
-	parAgent.Run(ctx, &AgentInput{}, withValue("Agent1").DesignateAgent("Agent1"), withValue("Agent2").DesignateAgent("Agent2"))
-
+	iter = parAgent.Run(ctx, &AgentInput{}, withValue("Agent1").DesignateAgent("Agent1"), withValue("Agent2").DesignateAgent("Agent2"))
+	_, ok = iter.Next()
+	assert.False(t, ok)
 }
diff --git a/schema/serialization_test.go b/schema/serialization_test.go
index 6b0cf11..347bb3a 100644
--- a/schema/serialization_test.go
+++ b/schema/serialization_test.go
@@ -114,7 +114,6 @@ func TestRegister(t *testing.T) {
 
 	Register[*testStruct1]()
 	Register[*testStruct2]()
-	Register[[]*Message]()
 	Register[[]Message]()
 	Register[[]*testStruct2]()
 	Register[[]testStruct2]()