Eino Study Notes — Appendix 1 - MuxiaoWFSkip to main content
This page was machine-translated and may contain errors or omissions. / 本页面为机器翻译,可能存在错漏。

Eino Study Notes — Appendix 1

Eino Study Notes — Appendix 1: The Flow Module — ReAct Agent and MultiAgent

Mon Jul 20 2026
1539 words · 12 minutes

This note was generated with AI assistance, but the code-reading order should be fine (all examples use the official Eino examples; personally, I find the official tutorials a bit hard to follow).

In principle, I think the appendices are just for reference — you can skim them and look things up when needed.

Appendix 1: The Flow Module — ReAct Agent and MultiAgent

📂 Source repo: github.com/cloudwego/eino-examples

📖 Series docs: Intro Notes | Agentic Advanced | Appendix 1 Flow | Appendix 2 Components | Appendix 3 Tools

📁 Code root: flow/agent/

📖 Prerequisites: This appendix assumes you have already mastered the content of the Intro Notes (sections 1–7) and Agentic Advanced.


At a Glance: Flow vs ADK — Two Systems

Eino provides two parallel ways to build Agents:

DimensionADK (Intro Notes / Agentic Advanced)Flow (this appendix)
Entry pointadk.NewChatModelAgent / adk.NewTypedChatModelAgentreact.NewAgent / host.NewMultiAgent / Compose Graph
Abstraction levelHigh-level wrapper, works out of the boxMid-to-low level, fine-grained control over every node
OrchestrationAgent + Runner + Middleware (declarative)Graph + Node + Branch + State (imperative)
Use casesQuickly build standard Agent appsCustom control flow, multi-Agent collaboration, fine-grained state management

🧠 How to choose: Prefer ADK for scenarios it can cover. Use Flow when you need to customize ReAct loop behavior, dynamically modify model parameters at runtime, or manage multi-Agent state-graph transitions.


Technical Selection Decision Tree

graph TD
    Q["What do you want to do?"] --> Q1{"Standard single Agent
Q&A + tools + memory?"} Q1 -->|Yes| ADK["Use ADK
Intro Notes §3 / Agentic Advanced §3"] Q1 -->|No| Q2{"Need custom
ReAct loop behavior?"} Q2 -->|Yes| REACT["Use react.NewAgent
this appendix §1/§2/§4"] Q2 -->|No| Q3{"Need multi-Agent collaboration?"} Q3 -->|Orchestration mode| SUP["Quick: ADK Supervisor
Fine-grained: Host MultiAgent
this appendix §6"] Q3 -->|Pipeline| PE["Plan-Execute
this appendix §7"] Q3 -->|Complex state graph| CUSTOM["Custom Graph+State
this appendix §9"] Q3 -->|No| Q4{"Need browser/sandbox tools?"} Q4 -->|Yes| MANUS["See Manus Agent
this appendix §8"]

📇 Quick-Reference Card for This Appendix

react.NewAgent({ToolCallingModel, ToolsConfig}) → Stream/Generate
react.WithMessageFuture() → observe intermediate steps (no Callback needed)
host.NewMultiAgent({Host, Specialists}) → orchestration-style multi-Agent
Custom Graph + State → Plan-Execute / Deer-Go

1. ReAct Agent Basics

📁 flow/agent/react/

📖 Official docs: https://www.cloudwego.io/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual/

1.1 What is ReAct

ReAct (Reasoning + Acting) is the core loop pattern of an Agent:

User input → ChatModel thinks → Need to call a Tool?
├─ Yes → Execute Tool → Tool result back to ChatModel → keep thinking
└─ No → Output final answer

Eino wraps this loop as react.NewAgent — create it in one line, ready to use out of the box.

1.2 Minimal Example: Restaurant Recommendation

import (
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/compose"
)
rAgent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: arkModel,
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{restaurantTool, dishTool},
},
})
// Streaming call (same Recv+EOF pattern as ChatModel.Stream)
sr, _ := rAgent.Stream(ctx, []*schema.Message{
schema.SystemMessage("You are an assistant that helps users find restaurants..."),
schema.UserMessage("I'm in Beijing, recommend spicy dishes, at least 2 restaurants"),
})
defer sr.Close()
for {
msg, err := sr.Recv()
if errors.Is(err, io.EOF) { break }
fmt.Print(msg.Content)
}

💡 This is functionally equivalent to the ChatModelAgent + Runner from Intro Notes section 3, but react.NewAgent exposes more low-level control points.

1.3 Two Important Extension Points

MessageModifier: Inject a System Prompt into each ReAct loop iteration:

react.NewAgent(ctx, &react.AgentConfig{
MessageModifier: func(_ context.Context, input []*schema.Message) []*schema.Message {
return append([]*schema.Message{schema.SystemMessage(sys)}, input...)
},
})

StreamToolCallChecker: Customize Tool-call detection (some models don’t return ToolCall in the first chunk):

toolCallChecker := func(ctx context.Context, sr *schema.StreamReader[*schema.Message]) (bool, error) {
defer sr.Close()
for {
msg, err := sr.Recv()
if errors.Is(err, io.EOF) { break }
if len(msg.ToolCalls) > 0 { return true, nil }
}
return false, nil
}

1.4 Visual Debugging: ExportGraph + Mermaid

anyG, opts := rAgent.ExportGraph()
gen := visualize.NewMermaidGenerator("flow/agent/react")
g := compose.NewGraph[[]*schema.Message, *schema.Message]()
g.AddGraphNode("react_agent", anyG, opts...)
g.AddEdge(compose.START, "react_agent")
g.AddEdge("react_agent", compose.END)
g.Compile(context.Background(), compose.WithGraphCompileCallbacks(gen))

2. Agentic ReAct Agent (Responses API Version)

📁 flow/agent/react/agentic/

2.1 Core Differences

DimensionBasic ReActAgentic ReAct
Modelmodel.BaseChatModelmodel.AgenticModel
Message*schema.Message*schema.AgenticMessage
Tool nodecompose.NewToolNodecompose.NewAgenticToolsNode
Branch decisionCheck ToolCallsCheck FunctionToolCall in ContentBlocks
Server-side tools

2.2 Key Innovation: ToolReturnDirectly

config := &AgentConfig{
Model: am,
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{summarizeTool, locationTool},
},
ToolReturnDirectly: map[string]struct{}{
"summarize_news": {}, // ← summarize_news finishes execution and ends directly
},
}

2.3 Handling by ContentBlock Type

msg, _ := schema.ConcatAgenticMessages(msgs)
for _, block := range msg.ContentBlocks {
switch block.Type {
case schema.ContentBlockTypeReasoning:
fmt.Printf("[Reasoning] %s\n", block.Reasoning.Text)
case schema.ContentBlockTypeServerToolCall:
fmt.Printf("[Server tool] %s\n", block.ServerToolCall.Name)
case schema.ContentBlockTypeAssistantGenText:
fmt.Printf("[Output] %s\n", block.AssistantGenText.Text)
}
}

3. Short-Term Memory

📁 flow/agent/react/memory_example/

3.1 MemoryStore Interface

type MemoryStore interface {
Write(ctx, sessionID string, msgs []*schema.Message) error
Read(ctx, sessionID string) ([]*schema.Message, error)
Query(ctx, sessionID, text string, limit int) ([]*schema.Message, error)
}
ImplementationStorageUse case
InMemoryStoreProcess memoryDevelopment/testing
RedisStoreRedisProduction

3.2 Multi-Turn Conversation Pattern

store := memory.NewInMemoryStore()
sessionID := "session:demo"
// Each conversation turn
prev, _ := store.Read(ctx, sessionID) // Restore history
eff := append(prev, schema.UserMessage(turn)) // Concatenate new input
sr, _ := agent.Stream(ctx, eff, msgFutureOpt) // Execute
_ = store.Write(ctx, sessionID, append(eff, produced...)) // Persist

3.3 Observing Intermediate Steps with MessageFuture

msgFutureOpt, msgFuture := react.WithMessageFuture()
// Consume the intermediate message stream
iter := msgFuture.GetMessageStreams()
for {
sr, ok, _ := iter.Next()
if !ok { break }
full, _ := schema.ConcatMessages(readAllChunks(sr))
fmt.Printf("Intermediate message: role=%s content=%s\n", full.Role, full.Content)
}

4. Dynamic Options

📁 flow/agent/react/dynamic_option_example/

4.1 The Pain Point

The ReAct Agent uses the same model parameters in every iteration. But you might want thinking mode enabled on the first round and disabled in later rounds.

4.2 Core Mechanism: Wrap ChatModel + ProcessState

type ChatModel struct {
Model model.BaseChatModel
GetOptionFunc OptionFunc // ← Returns dynamic options based on State
}
func (d *ChatModel) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) {
var dynamicOpts []model.Option
compose.ProcessState[*State](ctx, func(_ context.Context, state *State) error {
dynamicOpts = d.GetOptionFunc(ctx, input, state)
state.Iteration++
return nil
})
return d.Model.Generate(ctx, input, append(dynamicOpts, opts...)...)
}

4.3 Dynamic Option Function Example

func getDynamicOptions(ctx context.Context, input []*schema.Message, state *State) []model.Option {
if state.Iteration >= 1 {
// From the second round on: disable thinking, forbid Tool calls
return []model.Option{
ark.WithThinking(&arkModel.Thinking{Type: arkModel.ThinkingTypeDisabled}),
model.WithToolChoice(schema.ToolChoiceForbidden),
}
}
// First round: allow Tool calls
return []model.Option{
model.WithToolChoice(schema.ToolChoiceAllowed),
model.WithTools(toolInfos),
}
}

5. Unknown Tool Handling (Unknown Tool Handler)

📁 flow/agent/react/unknown_tool_handler_example/

rAgent, _ := react.NewAgent(ctx, &react.AgentConfig{
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{sumTool},
UnknownToolsHandler: func(ctx context.Context, name, input string) (string, error) {
return fmt.Sprintf(
"unknown tool: %s; try again with the correct tool name", name,
), nil
},
},
})

The model hallucinates and calls a non-existent tool → no crash, returns a hint → the model self-corrects.


6. Multi-Agent: Host Mode

📁 flow/agent/multiagent/host/journal/

📖 Official docs: https://www.cloudwego.io/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting/

6.1 Concept

One Host (dispatcher) + multiple Specialists (experts):

User → Host (analyze intent)
├─ "Write journal" → WriteJournalSpecialist
├─ "Read journal" → ReadJournalSpecialist
└─ "Query journal" → AnswerWithJournalSpecialist

6.2 Full Implementation

import "github.com/cloudwego/eino/flow/agent/multiagent/host"
h := &host.Host{ChatModel: chatModel, SystemPrompt: "..."}
hostMA, _ := host.NewMultiAgent(ctx, &host.MultiAgentConfig{
Host: *h,
Specialists: []*host.Specialist{writer, reader, answerer},
})
// Multi-turn conversation
out, _ := hostMA.Stream(ctx, []*schema.Message{schema.UserMessage(input)},
host.WithAgentCallbacks(cb), // Can listen to HandOff events
)

6.3 Comparison with ADK Supervisor

DimensionHost MultiAgent (Flow)Supervisor (ADK)
Locationflow/agent/multiagent/hostadk/prebuilt/supervisor
DispatchHost ChatModel decides who to assign toSupervisor Agent decides who to assign to
AbstractionMid-to-low level (Compose Graph)High level (ADK wrapper)
Use caseNeed custom Specialist structureQuickly build multi-Agent systems

7. Multi-Agent: Plan-Execute Mode

📁 flow/agent/multiagent/plan_execute/

7.1 Concept

A three-stage collaboration pattern:

Planner → Executor ⇄ Reviser
Output "final answer" → END

7.2 Core Configuration

config := &Config{
PlannerModel: deepSeekModel, // Planner model
ExecutorModel: arkModel, // Executor model (must support ToolCalling)
ReviserModel: deepSeekModel, // Reviser model
ToolsConfig: compose.ToolsNodeConfig{Tools: toolsConfig},
MaxStep: 100,
}

💡 Different roles can use different models — Planner/Reviser use cheaper reasoning models, Executor uses a model that supports Tool Calling.


8. Full Application: Manus Agent

📁 flow/agent/manus/

8.1 Overview

Manus Agent demonstrates how to build an Agent with the following capabilities using the Compose Graph API:

  • 🖥️ Command-line execution: Run Python code in a Docker sandbox
  • 🌐 Browser operation: Web browsing and interaction based on Playwright
  • 🔍 Search engine: DuckDuckGo integration
  • 👤 Human feedback: Interrupt execution, wait for user confirmation, then continue
  • 📊 Observability: Dual tracing via Langfuse + CozeLoop

8.2 Interrupt-Driven Human Feedback Loop

for {
result, err := agent.Invoke(ctx, input,
compose.WithCheckPointID("1"),
compose.WithRuntimeMaxSteps(20),
)
info, ok := compose.ExtractInterruptInfo(err)
if ok {
// Interrupted — show the result, wait for user confirmation
fmt.Print("Do you want to continue? (y/n): ")
// ... read user input ...
continue // Auto Resume
}
fmt.Printf("[FinalResult]: %s", result)
break
}

8.3 Tool Ecosystem

// Command-line tool (Docker sandbox)
sandbox.NewDockerSandbox(ctx, &sandbox.Config{...})
// Browser tool
browseruse.NewBrowserUseTool(ctx, &browseruse.Config{...})
// Search engine
duckduckgo.NewSearch(ctx, &duckduckgo.Config{...})

9. Full Application: Deer-Go (Research Team Collaboration)

📁 flow/agent/deer-go/

Reference: https://github.com/bytedance/deer-flow

9.1 Overview

Deer-Go is a state-graph-based multi-Agent research team that simulates a real research collaboration workflow.

9.2 Core Mechanism: State-Driven Subgraph Transitions

After each subgraph finishes executing, it overwrites state.Goto to specify the next Agent to run:

func agentHandOff(ctx context.Context, input string) (next string, err error) {
compose.ProcessState[*model.State](ctx, func(_ context.Context, state *model.State) error {
next = state.Goto
return nil
})
return next, nil
}
// All Agent nodes share the same Branch function
g.AddBranch(consts.Coordinator, compose.NewGraphBranch(agentHandOff, outMap))
g.AddBranch(consts.Planner, compose.NewGraphBranch(agentHandOff, outMap))

9.3 Team Composition

Coordinator → BackgroundInvestigator → Planner → ResearchTeam
├─→ Researcher
├─→ Coder
└─→ Planner (iterate)
└─→ Reporter → Human → Coordinator

10. Example Navigation

DirectoryTopicCore knowledgeComplexity
react/ReAct Agent basicsreact.NewAgent, Stream, ExportGraph
react/agentic/Agentic ReActCustom Graph ReAct, ToolReturnDirectly⭐⭐⭐
react/memory_example/Short-term memoryMemoryStore interface, MessageFuture⭐⭐
react/dynamic_option_example/Dynamic optionsChatModel wrapper, ProcessState⭐⭐⭐
react/unknown_tool_handler_example/Unknown toolsUnknownToolsHandler
multiagent/host/journal/Host Multi-AgentHost + Specialist, HandOff callback⭐⭐
multiagent/plan_execute/Plan-ExecutePlanner→Executor⇄Reviser⭐⭐⭐
manus/Manus AgentDocker sandbox, browser, Interrupt+Resume⭐⭐⭐⭐
deer-go/Deer-Go research teamState-graph transitions, multi-subgraph collaboration, Goto-driven⭐⭐⭐⭐

Common import paths

// ReAct Agent
"github.com/cloudwego/eino/flow/agent/react" // react.NewAgent, WithMessageFuture
// Multi-Agent
"github.com/cloudwego/eino/flow/agent/multiagent/host" // host.NewMultiAgent, Host, Specialist
// Compose low-level
"github.com/cloudwego/eino/compose" // Graph, Branch, ProcessState, ToolNode
"github.com/cloudwego/eino/schema" // Message, AgenticMessage, ToolInfo
// Callbacks
"github.com/cloudwego/eino/callbacks" // Handler, HandlerBuilder
"github.com/cloudwego/eino/utils/callbacks" // NewHandlerHelper

📇 Quick-Reference Card for This Appendix

Flow module core methods:
react.NewAgent({ToolCallingModel, ToolsConfig})
host.NewMultiAgent({Host, Specialists})
compose.NewGraphBranch(fn, outMap)
compose.ProcessState[T](ctx, fn) — read/write Graph State
Selection: Standard→ADK | Custom loop→react | Multi-Agent dispatch→host | State graph→Graph+State

📂 Source repo: github.com/cloudwego/eino-examples

📖 Keep reading:


Thanks for reading! Follow me if you'd like~

Eino Study Notes — Appendix 1

Mon Jul 20 2026
1539 words · 12 minutes
Cover
Sample track
Sample artist
Cover
Sample track
Sample artist
0:00 / 0:00