diff --git a/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Local_FileSystem.md b/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Local_FileSystem.md
index a81f29354c..45d69c9eb1 100644
--- a/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Local_FileSystem.md
+++ b/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Local_FileSystem.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-02"
+date: "2026-03-12"
lastmod: ""
tags: []
title: 'Backend: Local FileSystem'
@@ -11,7 +11,7 @@ weight: 2
Package: `github.com/cloudwego/eino-ext/adk/backend/local`
-Note: If your eino version is v0.8.0 or above, you need to use local backend [v0.2.0-alpha](https://github.com/cloudwego/eino-ext/releases/tag/adk%2Fbackend%2Flocal%2Fv0.2.0-alpha.1) version.
+Note: If your eino version is v0.8.0 or above, you need to use local backend [adk/backend/local/v0.2.1](https://github.com/cloudwego/eino-ext/releases/tag/adk%2Fbackend%2Flocal%2Fv0.2.1).
### Overview
diff --git a/content/en/docs/eino/ecosystem_integration/_index.md b/content/en/docs/eino/ecosystem_integration/_index.md
index fd408ba5e1..7c831883a7 100644
--- a/content/en/docs/eino/ecosystem_integration/_index.md
+++ b/content/en/docs/eino/ecosystem_integration/_index.md
@@ -11,9 +11,9 @@ weight: 6
### ChatModel
-- openai: [ChatModel - OpenAI](https://github.com/cloudwego/eino-ext/blob/main/components/model/openai/README.md)
-- ark: [ChatModel - ARK](https://github.com/cloudwego/eino-ext/blob/main/components/model/ark/README.md)
-- ollama: [ChatModel - Ollama](https://github.com/cloudwego/eino-ext/blob/main/components/model/ollama/README.md)
+- openai: [OpenAI](/docs/eino/ecosystem_integration/chat_model/agentic_model_openai)
+- ark: [ARK](/docs/eino/ecosystem_integration/chat_model/agentic_model_ark)
+- More components: [ChatModel component list](/docs/eino/ecosystem_integration/chat_model)
### Document
diff --git a/content/en/docs/eino/quick_start/_index.md b/content/en/docs/eino/quick_start/_index.md
index 505844a43b..0a3a00bff4 100644
--- a/content/en/docs/eino/quick_start/_index.md
+++ b/content/en/docs/eino/quick_start/_index.md
@@ -19,7 +19,7 @@ These small examples are meant for quick onboarding. For deeper dives into speci
The most basic pattern in LLM applications is a `prompt + chat model`, which is also the primary capability offered by many AI platforms. You can define a `System Prompt` to constrain the model’s behavior (for example, “You are acting as role XXX”). In this example, you can combine Eino’s `PromptTemplate` and `ChatModel` components to build a role-playing application.
-- [Implement a minimal LLM application — ChatModel](/docs/eino/quick_start/simple_llm_application)
+- [Chapter 1: ChatModel and Message (Console)](/docs/eino/quick_start/chapter_01_chatmodel_and_message)
### Example: Build an Agent
@@ -29,12 +29,11 @@ We call the overall system that decides when to call specific tools based on mod
In Eino, you can implement an agent with `ChatModel + ToolsNode`, or use the built-in `react agent` and `multi agent` packages.
-In this example, we’ll use the ReAct agent to build an agent that interacts with the real world.
+In this example, we’ll use a filesystem tool to build an agent that can interact with the real world.
-- [Agent — Give your LLM hands](/docs/eino/quick_start/agent_llm_with_tools)
+- [Chapter 4: Tools and Filesystem Access](/docs/eino/quick_start/chapter_04_tool_and_filesystem)
## Next Steps
- Understand Eino’s core modules and concepts: [Eino: Core Modules](/docs/eino/core_modules). This is the key knowledge to fluently develop applications with Eino.
- Eino embraces an open ecosystem and provides numerous integration components: [Eino: Ecosystem Integration](/docs/eino/ecosystem_integration). Use these components to quickly assemble your business applications.
-
diff --git a/content/en/docs/eino/quick_start/agent_llm_with_tools.md b/content/en/docs/eino/quick_start/agent_llm_with_tools.md
deleted file mode 100644
index e0e17f6981..0000000000
--- a/content/en/docs/eino/quick_start/agent_llm_with_tools.md
+++ /dev/null
@@ -1,300 +0,0 @@
----
-Description: ""
-date: "2026-03-03"
-lastmod: ""
-tags: []
-title: Agent — Give Your LLM Hands
-weight: 2
----
-
-## What Is an Agent?
-
-An Agent is a system that perceives its environment and takes actions to achieve a goal. In AI applications, agents combine the language understanding of LLMs with tool execution, enabling them to autonomously complete complex tasks — a key form factor for how AI integrates into everyday work and life.
-
-> 💡
-> Example code snippets: [eino-examples/quickstart/todoagent](https://github.com/cloudwego/eino-examples/blob/master/quickstart/todoagent/main.go)
-
-## Core Components of an Agent
-
-In Eino, an agent typically consists of two core parts: a `ChatModel` and one or more `Tools`.
-
-### ChatModel
-
-`ChatModel` is the agent’s brain. It processes the user’s natural language input, understands intent, analyzes requirements, and decides whether a tool is needed. When tools are required, it selects the right tool with the right parameters and converts tool outputs back into natural-language responses.
-
-> More about ChatModel: [Eino: ChatModel Guide](/docs/eino/core_modules/components/chat_model_guide)
-
-### Tool
-
-`Tool` is the agent’s executor. Each tool has a clear function definition and parameter schema, allowing the `ChatModel` to call it accurately. Tools can wrap anything from simple data ops to sophisticated external service calls.
-
-> More about tools and ToolsNode: [Eino: ToolsNode Guide](/docs/eino/core_modules/components/tools_node_guide)
-
-## Implementing Tools
-
-Eino offers multiple ways to implement tools. We illustrate with a simple Todo management system.
-
-### Approach 1: Build with `NewTool`
-
-This is ideal for simpler tools: define tool metadata and a handler function.
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/components/tool/utils"
- "github.com/cloudwego/eino/schema"
-)
-
-// Handler
-func AddTodoFunc(_ context.Context, params *TodoAddParams) (string, error) {
- // Mock
- return `{"msg": "add todo success"}`, nil
-}
-
-func getAddTodoTool() tool.InvokableTool {
- // Tool metadata
- info := &schema.ToolInfo{
- Name: "add_todo",
- Desc: "Add a todo item",
- ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
- "content": {
- Desc: "The content of the todo item",
- Type: schema.String,
- Required: true,
- },
- "started_at": {
- Desc: "The started time of the todo item, in unix timestamp",
- Type: schema.Integer,
- },
- "deadline": {
- Desc: "The deadline of the todo item, in unix timestamp",
- Type: schema.Integer,
- },
- }),
- }
-
- // Build with NewTool
- return utils.NewTool(info, AddTodoFunc)
-}
-```
-
-This approach is straightforward but has a drawback: parameter descriptions (`ParamsOneOf`) are separate from the actual parameter struct (`TodoAddParams`). Changes require updating both, risking inconsistency.
-
-### Approach 2: Build with `InferTool`
-
-This is more concise. Use struct tags to define parameter metadata so the description and struct share the same source.
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool/utils"
-)
-
-// Parameter struct
-type TodoUpdateParams struct {
- ID string `json:"id" jsonschema:"description=id of the todo"`
- Content *string `json:"content,omitempty" jsonschema:"description=content of the todo"`
- StartedAt *int64 `json:"started_at,omitempty" jsonschema:"description=start time in unix timestamp"`
- Deadline *int64 `json:"deadline,omitempty" jsonschema:"description=deadline of the todo in unix timestamp"`
- Done *bool `json:"done,omitempty" jsonschema:"description=done status"`
-}
-
-// Handler
-func UpdateTodoFunc(_ context.Context, params *TodoUpdateParams) (string, error) {
- // Mock
- return `{"msg": "update todo success"}`, nil
-}
-
-// Build tool with InferTool
-updateTool, err := utils.InferTool(
- "update_todo", // tool name
- "Update a todo item, eg: content,deadline...", // description
- UpdateTodoFunc)
-```
-
-### Approach 3: Implement the Tool Interface
-
-For advanced scenarios, implement the `Tool` interface.
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/schema"
-)
-
-type ListTodoTool struct {}
-
-func (lt *ListTodoTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
- return &schema.ToolInfo{
- Name: "list_todo",
- Desc: "List all todo items",
- ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
- "finished": {
- Desc: "filter todo items if finished",
- Type: schema.Boolean,
- Required: false,
- },
- }),
- }, nil
-}
-
-func (lt *ListTodoTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
- // Mock
- return `{"todos": [{"id": "1", "content": "Prepare Eino demo slides before 2024-12-10", "started_at": 1717401600, "deadline": 1717488000, "done": false}]}` , nil
-}
-```
-
-### Approach 4: Use Official Tools
-
-Beyond custom tools, Eino provides many well-tested, ready-to-use tools. For example, DuckDuckGo Search:
-
-```go
-import (
- "github.com/cloudwego/eino-ext/components/tool/duckduckgo"
-)
-
-
-// Create DuckDuckGo Search tool
-searchTool, err := duckduckgo.NewTool(ctx, &duckduckgo.Config{})
-```
-
-Using tools from `eino-ext` avoids reinvention and ensures reliability — they’re maintained and continuously improved.
-
-## Build an Agent with Chain
-
-`ToolsNode` is a core component for agents, managing tool invocation. It can host multiple tools and supports both synchronous (`Invoke`) and streaming (`Stream`) execution.
-
-To create a `ToolsNode`, provide a tool list configuration:
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/compose"
-)
-
-conf := &compose.ToolsNodeConfig{
- Tools: []tool.BaseTool{tool1, tool2}, // tools can be InvokableTool or StreamableTool
-}
-toolsNode, err := compose.NewToolNode(context.Background(), conf)
-```
-
-Below is a complete agent example using OpenAI’s `ChatModel` and the Todo tools above:
-
-```go
-import (
- "context"
- "fmt"
- "log"
- "os"
-
- "github.com/cloudwego/eino-ext/components/model/openai"
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/compose"
- "github.com/cloudwego/eino/schema"
-)
-
-func main() {
- // Initialize tools
- todoTools := []tool.BaseTool{
- getAddTodoTool(), // NewTool
- updateTool, // InferTool
- &ListTodoTool{}, // Implement Tool interface
- searchTool, // Official tool
- }
-
- // Create and configure ChatModel
- chatModel, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{
- Model: "gpt-4",
- APIKey: os.Getenv("OPENAI_API_KEY"),
- })
- if err != nil {
- log.Fatal(err)
- }
- // Bind tool infos to ChatModel
- toolInfos := make([]*schema.ToolInfo, 0, len(todoTools))
- for _, tool := range todoTools {
- info, err := tool.Info(ctx)
- if err != nil {
- log.Fatal(err)
- }
- toolInfos = append(toolInfos, info)
- }
- err = chatModel.BindTools(toolInfos)
- if err != nil {
- log.Fatal(err)
- }
-
-
- // Create tools node
- todoToolsNode, err := compose.NewToolNode(context.Background(), &compose.ToolsNodeConfig{
- Tools: todoTools,
- })
- if err != nil {
- log.Fatal(err)
- }
-
- // Build chain
- chain := compose.NewChain[[]*schema.Message, []*schema.Message]()
- chain.
- AppendChatModel(chatModel, compose.WithNodeName("chat_model")).
- AppendToolsNode(todoToolsNode, compose.WithNodeName("tools"))
-
- // Compile and run
- agent, err := chain.Compile(ctx)
- if err != nil {
- log.Fatal(err)
- }
-
- // Run example
- resp, err := agent.Invoke(ctx, []*schema.Message{
- {
- Role: schema.User,
- Content: "Add a TODO to learn Eino and search for the cloudwego/eino repo URL",
- },
- })
- if err != nil {
- log.Fatal(err)
- }
-
- // Print output
- for _, msg := range resp {
- fmt.Println(msg.Content)
- }
-}
-```
-
-This example assumes the `ChatModel` will decide to make tool calls when appropriate.
-
-## Other Ways to Build Agents
-
-Beyond Chain/Graph-based agents, Eino provides ready-made agent patterns.
-
-### ReAct Agent
-
-ReAct (Reasoning + Acting) combines deep reasoning with action through a think–act–observe loop. It’s well-suited for multi-step reasoning in complex tasks.
-
-> Learn more: [Eino: ReAct Agent Manual](/docs/eino/core_modules/flow_integration_components/react_agent_manual)
-
-### Multi Agent
-
-Multi-agent systems coordinate multiple agents, each with distinct responsibilities and expertise. Through interaction and collaboration, they can tackle complex tasks requiring multiple areas of knowledge.
-
-> Learn more: [Eino Tutorial: Host Multi-Agent](/docs/eino/core_modules/flow_integration_components/multi_agent_hosting)
-
-## Summary
-
-This article introduced core approaches to building agents with Eino. Using chains, tool calling, or ReAct patterns, you can flexibly construct AI agents to meet practical needs.
-
-Agents are a vital direction in AI — they understand user intent and take action by calling tools to accomplish complex tasks. As LLMs advance, agents will increasingly bridge AI and the real world. We hope Eino helps you build powerful, user-friendly agents and inspires new agent-driven applications.
-
-## Related Reading
-
-- Quick Start
- - [Build a Minimal LLM Application — ChatModel](/docs/eino/quick_start/simple_llm_application)
diff --git a/content/en/docs/eino/quick_start/chapter_01_chatmodel_and_message.md b/content/en/docs/eino/quick_start/chapter_01_chatmodel_and_message.md
new file mode 100644
index 0000000000..472dcaf564
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_01_chatmodel_and_message.md
@@ -0,0 +1,222 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 1: ChatModel and Message (Console)"
+weight: 1
+---
+
+## Introduction to the Eino Framework
+
+**What is Eino?**
+
+Eino is a Go-based AI application development framework (Agent Development Kit) designed to help developers build AI applications that are extensible and maintainable.
+
+**What problems does Eino solve?**
+
+1. **Model abstraction**: A unified interface over different LLM providers (OpenAI, Ark, Claude, etc.). Switching models does not require business-code changes.
+2. **Capability composition**: Replaceable, composable capability units via the Component interfaces (chat, tools, retrieval, etc.).
+3. **Orchestration framework**: Agent, Graph, and Chain abstractions for complex multi-step AI workflows.
+4. **Runtime support**: Streaming output, interrupt/resume, state management, and callback observability.
+
+**The main repositories:**
+
+- **eino** (this repo): core library, defines interfaces, orchestration abstractions, and ADK
+- **eino-ext**: extensions, concrete implementations of components (OpenAI, Ark, Milvus, etc.)
+- **eino-examples**: example code, including this quickstart series
+
+---
+
+## ChatWithEino: An Assistant That Talks With Eino Docs
+
+**What is ChatWithEino?**
+
+ChatWithEino is an assistant built with the Eino framework to help developers learn Eino and write Eino code. It answers questions based on the Eino repositories’ source code, comments, and examples.
+
+**Core capabilities:**
+
+- **Conversational interaction**: Understand Eino-related questions and provide clear answers
+- **Code access**: Read Eino source code, comments, and examples to answer based on real implementations
+- **Persistent sessions**: Multi-turn conversations with remembered context, recoverable across processes
+- **Tool calling**: Execute operations like file reads and code search
+
+**Architecture:**
+
+- **ChatModel**: Communicate with LLMs (OpenAI, Ark, Claude, etc.)
+- **Tool**: Capability extensions such as filesystem access and code search
+- **Memory**: Persist conversation history
+- **Agent**: A unified execution framework coordinating all components
+
+## Quickstart Series: Build ChatWithEino From Scratch
+
+This series starts from the simplest ChatModel call and incrementally builds a complete ChatWithEino Agent.
+
+**Learning path:**
+
+
+| Chapter | Topic | What you build | Capability |
+| Chapter 1 | ChatModel and Message | Understand the Component abstraction, implement a single-turn chat | Basic chat |
+| Chapter 2 | Agent and Runner | Introduce the execution abstraction, implement multi-turn chat | Session management |
+| Chapter 3 | Memory and Session | Persist chat history and support session recovery | Persistence |
+| Chapter 4 | Tool and filesystem | Add file-access capability and read source code | Tool calling |
+| Chapter 5 | Middleware | Middleware mechanism to handle cross-cutting concerns | Extensibility |
+| Chapter 6 | Callback | Callback mechanism to observe Agent execution | Observability |
+| Chapter 7 | Interrupt and Resume | Interrupt/resume for long-running tasks | Reliability |
+| Chapter 8 | Graph and Tool | Use Graph to orchestrate complex workflows | Advanced orchestration |
+| Chapter 9 | A2UI | Integration from Agent to UI | Production readiness |
+
+
+**Why this design?**
+
+Each chapter adds one core capability on top of the previous one, so you can:
+
+1. **Understand each component’s role**: Introduce features gradually instead of all at once
+2. **See how the architecture evolves**: From simple to complex, and why each abstraction exists
+3. **Build practical skills**: Every chapter includes runnable code for hands-on practice
+
+---
+
+Goal of this chapter: understand Eino’s Component abstraction, call a ChatModel with minimal code (with streaming output), and learn the basic usage of `schema.Message`.
+
+## Code Location
+
+- Entry code: [cmd/ch01/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch01/main.go)
+
+## Why a Component Interface?
+
+Eino defines a set of Component interfaces (`ChatModel`, `Tool`, `Retriever`, `Loader`, etc.). Each interface describes a replaceable capability:
+
+```go
+type BaseChatModel interface {
+ Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error)
+ Stream(ctx context.Context, input []*schema.Message, opts ...Option) (
+ *schema.StreamReader[*schema.Message], error)
+}
+```
+
+**Benefits of interfaces:**
+
+1. **Replaceable implementations**: `eino-ext` provides OpenAI, Ark, Claude, Ollama, and more. Business code depends only on the interface; switching models only changes construction.
+2. **Composable orchestration**: Agent/Graph/Chain depend only on Component interfaces, not implementations. Replace OpenAI with Ark without changing orchestration code.
+3. **Mock-friendly testing**: Interfaces are easy to mock; unit tests do not need real model calls.
+
+This chapter focuses only on `ChatModel`. Later chapters will introduce `Tool`, `Retriever`, and more.
+
+## schema.Message: The Basic Unit of a Conversation
+
+`Message` is the basic structure for conversation data in Eino:
+
+```go
+type Message struct {
+ Role RoleType // system / user / assistant / tool
+ Content string // text content
+ ToolCalls []ToolCall // only assistant messages may have this
+ // ...
+}
+```
+
+Common constructors:
+
+```go
+schema.SystemMessage("You are a helpful assistant.")
+schema.UserMessage("What is the weather today?")
+schema.AssistantMessage("I don't know.", nil) // the second argument is ToolCalls
+schema.ToolMessage("tool result", "call_id")
+```
+
+**Role semantics:**
+
+- `system`: system instruction, usually first in the message list
+- `user`: user input
+- `assistant`: model response
+- `tool`: tool execution result (used in later chapters)
+
+## Prerequisites
+
+### Get the Code
+
+```bash
+git clone https://github.com/cloudwego/eino-examples.git
+cd eino-examples/quickstart/chatwitheino
+```
+
+- Go version: Go 1.21+ (see `go.mod`)
+- A callable ChatModel (default: OpenAI; Ark is also supported)
+
+### Option A: OpenAI (Default)
+
+```bash
+export OPENAI_API_KEY="..."
+export OPENAI_MODEL="gpt-4.1-mini" # OpenAI model (2025), or gpt-4o / gpt-4o-mini, etc.
+# Optional:
+# OPENAI_BASE_URL (proxy or compatible service)
+# OPENAI_BY_AZURE=true (use Azure OpenAI)
+```
+
+### Option B: Ark
+
+```bash
+export MODEL_TYPE="ark"
+export ARK_API_KEY="..."
+export ARK_MODEL="..."
+# Optional: ARK_BASE_URL
+```
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+go run ./cmd/ch01 -- "In one sentence, what problem does Eino’s Component design solve?"
+```
+
+Example output (streaming print):
+
+```
+[assistant] Eino’s Component design defines unified interfaces...
+```
+
+## What the Entry Code Does
+
+In execution order:
+
+1. **Create the ChatModel**: select OpenAI or Ark based on the `MODEL_TYPE` environment variable
+2. **Build input messages**: `SystemMessage(instruction)` + `UserMessage(query)`
+3. **Call Stream**: all ChatModel implementations must support `Stream()`, which returns `StreamReader[*Message]`
+4. **Print output**: iterate over the `StreamReader` and print assistant chunks
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see the full code in [cmd/ch01/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch01/main.go)):
+
+```go
+// Build input
+messages := []*schema.Message{
+ schema.SystemMessage(instruction),
+ schema.UserMessage(query),
+}
+
+// Call Stream (all ChatModels must implement this)
+stream, err := cm.Stream(ctx, messages)
+if err != nil {
+ log.Fatal(err)
+}
+defer stream.Close()
+
+for {
+ chunk, err := stream.Recv()
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Print(chunk.Content)
+}
+```
+
+## Summary
+
+- **Component interfaces**: define replaceable, composable, and testable capability boundaries
+- **Message**: the basic unit of conversation data, with semantics defined by role
+- **ChatModel**: the fundamental Component, providing `Generate` and `Stream`
+- **Implementation choice**: switch between OpenAI/Ark via env/config without changing business code
diff --git a/content/en/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md b/content/en/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md
new file mode 100644
index 0000000000..589f34575c
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md
@@ -0,0 +1,309 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 2: ChatModelAgent, Runner, and AgentEvent (Console Multi-Turn)"
+weight: 2
+---
+
+Goal of this chapter: introduce the ADK execution abstraction (Agent + Runner) and implement a multi-turn conversation in a console program.
+
+## Code Location
+
+- Entry code: [cmd/ch02/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch02/main.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+go run ./cmd/ch02
+```
+
+After you see the prompt, type questions (empty line to exit):
+
+```
+you> Hi — what is an Agent in Eino?
+...
+you> Summarize it in one sentence
+...
+```
+
+## Key Concepts
+
+### From Component to Agent
+
+In Chapter 1 we learned **Components**: replaceable, composable capability units in Eino:
+
+- `ChatModel`: call an LLM
+- `Tool`: execute specific tasks
+- `Retriever`: retrieve information
+- `Loader`: load data
+
+**Relationship between Components and Agents:**
+
+- **A Component is not a complete AI app**: it is just a capability unit that must be organized/orchestrated/executed
+- **An Agent is a complete AI app**: it encapsulates business logic and can run directly
+- **Agents use Components internally**: the most fundamental are `ChatModel` (conversation) and `Tool` (actions)
+
+**Why do we need Agents?**
+
+If you only had Components, you would need to build a lot yourself:
+
+- manage conversation history
+- orchestrate call flow (when to call the model, when to call tools)
+- handle streaming output
+- implement interrupt/resume
+- ...
+
+**What does an Agent provide?**
+
+- **A complete runtime framework**: `Runner` uniformly manages execution
+- **A standard event-stream output**: `Run() -> AsyncIterator[*AgentEvent]`, enabling streaming, interrupt, and resume
+- **Extensibility**: add tools, middleware, interrupts, etc.
+- **Out of the box**: once created, an Agent can run without caring about internals
+
+**This chapter’s example:**
+
+`ChatModelAgent` is the simplest Agent. Internally it only uses `ChatModel`, but it already provides the full Agent runtime shape. Later chapters add `Tool` and more capabilities.
+
+### Agent Interface
+
+`Agent` is the core interface in ADK and defines the basic behavior of an agent:
+
+```go
+type Agent interface {
+ Name(ctx context.Context) string
+ Description(ctx context.Context) string
+
+ // Run executes the Agent and returns an event stream.
+ Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
+}
+```
+
+**Responsibilities:**
+
+- `Name()` / `Description()`: identify the Agent
+- `Run()`: execute the Agent, take input messages, and return an event stream
+
+**Design ideas:**
+
+- **Unified abstraction**: all Agents (ChatModelAgent, WorkflowAgent, SupervisorAgent, etc.) implement this interface
+- **Event-driven**: the execution is emitted as `AsyncIterator[*AgentEvent]` to support streaming responses
+- **Extensibility**: adding tools/middleware/interrupts later does not change the interface
+
+### ChatModelAgent
+
+`ChatModelAgent` is an implementation of the Agent interface built on a ChatModel:
+
+```go
+agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
+ Name: "Ch02ChatModelAgent",
+ Description: "A minimal ChatModelAgent with in-memory multi-turn history.",
+ Instruction: instruction,
+ Model: cm,
+})
+```
+
+**ChatModel vs ChatModelAgent: what’s the difference?**
+
+
+| Dimension | ChatModel | ChatModelAgent |
+| Role | Component | Agent |
+| Interface | Generate() / Stream() | Run() -> AsyncIterator[*AgentEvent] |
+| Output | Returns message content directly | Returns an event stream (messages + control actions) |
+| Capability | Pure model calling | Extensible with tools, middleware, interrupts, etc. |
+| When to use | Simple chat | Complex agent applications |
+
+
+**Why ChatModelAgent?**
+
+1. **Unified abstraction**: ChatModel is one Component; an Agent is a higher-level abstraction combining multiple Components
+2. **Event-driven output**: streaming response, interrupt/resume, state transitions, etc.
+3. **Extensibility**: add tools/middleware/interrupts; ChatModel itself only calls the model
+4. **Orchestration-friendly**: managed by Runner and supports runtime features like checkpoints and recovery
+
+**In short:**
+
+- **ChatModel** = “a component that talks to LLM providers and abstracts differences (OpenAI, Ark, Claude, etc.)”
+- **ChatModelAgent** = “an agent built on a model: it can call the model, and it can do more”
+
+**Analogy:**
+
+- **ChatModel** is like a “database driver”: hides differences between MySQL/PostgreSQL
+- **ChatModelAgent** is like the “business logic layer”: built on the driver, plus rules and runtime management
+
+**Characteristics:**
+
+- encapsulates ChatModel calling logic
+- provides a unified `Run() -> AgentEvent` output shape
+- can be extended with tools/middleware later
+
+### Runner
+
+`Runner` is the entry point for executing an Agent and manages the Agent lifecycle:
+
+```go
+type Runner struct {
+ a Agent // Agent to execute
+ enableStreaming bool
+ store CheckPointStore // state store for interrupt/resume
+}
+```
+
+**Why do we need Runner?**
+
+Although an Agent exposes `Run()`, calling it directly lacks many runtime capabilities:
+
+1. **Lifecycle management**: start/recover/interrupt states
+2. **Checkpoint support**: with `CheckPointStore` to implement interrupt/resume (later chapters)
+3. **Unified entry**: convenient methods like `Run()` and `Query()`
+4. **Event stream wrapping**: packages the agent’s output into a consumable `AsyncIterator[*AgentEvent]`
+
+**Usage:**
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+})
+
+// Option 1: pass a message list
+events := runner.Run(ctx, history)
+
+// Option 2: convenience method with a single query string
+events := runner.Query(ctx, "hello")
+```
+
+### AgentEvent
+
+`AgentEvent` is the event unit returned by Runner:
+
+```go
+type AgentEvent struct {
+ AgentName string
+ RunPath []RunStep
+
+ Output *AgentOutput // output content
+ Action *AgentAction // control actions
+ Err error // execution error
+}
+```
+
+**Key fields:**
+
+- `event.Err`: execution error
+- `event.Output.MessageOutput`: message or message stream (streaming)
+- `event.Action`: control actions such as interrupt/transition/exit (later chapters)
+
+### AsyncIterator: How to Consume an Event Stream
+
+`Runner.Run()` returns `*AsyncIterator[*AgentEvent]`, a non-blocking streaming iterator.
+
+**Why AsyncIterator instead of returning a final result directly?**
+
+Agent execution is **streaming**: the model generates token by token, with tool calls interleaved. Waiting for the full completion would increase perceived latency. `AsyncIterator` lets you consume events as they arrive.
+
+**Consumption pattern:**
+
+```go
+// events is *AsyncIterator[*AgentEvent], returned by runner.Run()
+events := runner.Run(ctx, history)
+
+for {
+ event, ok := events.Next() // blocks until there is an event or the stream ends
+ if !ok {
+ break // iterator closed, all events consumed
+ }
+ if event.Err != nil {
+ // handle error
+ }
+ if event.Output != nil && event.Output.MessageOutput != nil {
+ // handle message output (may be streaming)
+ }
+}
+```
+
+Note: each `runner.Run()` creates a new iterator. After consumption it cannot be reused.
+
+## Implementing Multi-Turn Conversation
+
+This chapter implements a simple multi-turn loop: user input → model reply → user input → ...
+
+**How it works:**
+
+Without tools, `ChatModelAgent` completes a single model call within one `Run()`. Multi-turn chat is implemented by the caller maintaining history:
+
+1. Keep `history []*schema.Message` as accumulated conversation
+2. For each user input, append `UserMessage` to history
+3. Call `runner.Run(ctx, history)` and consume the event stream to collect assistant text
+4. Append the assistant reply back into history and continue
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [cmd/ch02/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch02/main.go)):
+
+```go
+history := make([]*schema.Message, 0, 16)
+
+for {
+ // 1. Read user input
+ line := readUserInput()
+ if line == "" {
+ break
+ }
+
+ // 2. Append user message into history
+ history = append(history, schema.UserMessage(line))
+
+ // 3. Execute the Agent via Runner
+ events := runner.Run(ctx, history)
+
+ // 4. Consume the stream and collect assistant reply
+ content := collectAssistantFromEvents(events)
+
+ // 5. Append assistant message back into history
+ history = append(history, schema.AssistantMessage(content, nil))
+}
+```
+
+**Flow:**
+
+```
+┌─────────────────────────────────────────┐
+│ initialize history = [] │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ user input UserMessage│
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ append to history │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.Run(history) │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ consume event stream │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ append AssistantMessage│
+ └──────────────────────┘
+ ↓
+ (loop)
+```
+
+## Summary
+
+- **Agent interface**: defines the agent’s behavior; the core is `Run() -> AsyncIterator[*AgentEvent]`
+- **ChatModelAgent**: an Agent built on ChatModel, providing a unified execution abstraction
+- **Runner**: the execution entry, managing lifecycle, checkpoints, and event streams
+- **AgentEvent**: event-driven output unit supporting streaming and control actions
+- **Multi-turn conversation**: maintained by caller-side history; each `Run()` completes one turn
diff --git a/content/en/docs/eino/quick_start/chapter_03_memory_and_session.md b/content/en/docs/eino/quick_start/chapter_03_memory_and_session.md
new file mode 100644
index 0000000000..e48e1add4b
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_03_memory_and_session.md
@@ -0,0 +1,316 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 3: Memory and Session (Persistent Conversations)"
+weight: 3
+---
+
+Goal of this chapter: persist conversation history and support session recovery across processes.
+
+> Important: business-layer concepts vs framework concepts
+>
+> The **Memory**, **Session**, and **Store** described in this chapter are **business-layer concepts**, and **not core components of the Eino framework**.
+>
+> In other words, Eino is responsible for “how to process messages”, while “how to store messages” is entirely up to your application. The implementation shown here is a simple reference; you can choose a completely different storage solution (database, Redis, cloud storage, etc.).
+
+## Code Location
+
+- Entry code: [cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)
+- Memory implementation: [mem/store.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/mem/store.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# Create a new session
+go run ./cmd/ch03
+
+# Resume an existing session
+go run ./cmd/ch03 --session
+```
+
+Example output:
+
+```
+Created new session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+Session title: New Session
+Enter your message (empty line to exit):
+you> Hello, I am Zhang San
+[assistant] Hi Zhang San! Nice to meet you...
+you> What is my name?
+[assistant] Your name is Zhang San...
+
+Session saved: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+Resume with: go run ./cmd/ch03 --session 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+```
+
+## From In-Memory to Persistence: Why We Need “Memory”
+
+In Chapter 2 we implemented multi-turn chat, but there is a problem: **the conversation history exists only in memory**.
+
+**Limitations of in-memory storage:**
+
+- history is lost when the process exits
+- sessions cannot be recovered across devices/processes
+- session management (list/delete/search, etc.) is hard to implement
+
+**Positioning of “Memory”:**
+
+- **Memory is persistent storage for chat history**: save conversations to disk or a database
+- **Memory enables session management**: each Session represents one complete conversation
+- **Memory is decoupled from Agent**: the Agent does not care about storage details, only the message list
+
+**Analogy:**
+
+- **in-memory** = “scratch paper” (gone when the process exits)
+- **Memory** = “notebook” (saved permanently, can be reviewed anytime)
+
+## Key Concepts
+
+> Reminder: the Session/Store concepts below are **business-layer implementations** used to manage storage of chat history. Eino itself does not provide these components. Your application manages the message list, then passes it to `adk.Runner` for processing.
+
+### Session (Business-Layer Concept)
+
+`Session` represents a complete conversation:
+
+```go
+type Session struct {
+ ID string
+ CreatedAt time.Time
+
+ messages []*schema.Message // conversation history
+ // ...
+}
+```
+
+**Core methods:**
+
+- `Append(msg)`: append a message into the session and persist it
+- `GetMessages()`: get all messages
+- `Title()`: derive a title from the first user message
+
+### Store (Business-Layer Concept)
+
+`Store` manages persistent storage for multiple sessions:
+
+```go
+type Store struct {
+ dir string // storage directory
+ cache map[string]*Session // in-memory cache
+}
+```
+
+**Core methods:**
+
+- `GetOrCreate(id)`: get or create a session
+- `List()`: list all sessions
+- `Delete(id)`: delete a session
+
+### JSONL File Format
+
+Each Session is stored as a `.jsonl` file:
+
+```
+{"type":"session","id":"083d16da-...","created_at":"2026-03-11T10:00:00Z"}
+{"role":"user","content":"Hello, who am I?"}
+{"role":"assistant","content":"Hi! I don’t know who you are yet..."}
+{"role":"user","content":"My name is Zhang San"}
+{"role":"assistant","content":"Got it, Zhang San. Nice to meet you!"}
+```
+
+**Why JSONL?**
+
+- **Simple**: one JSON object per line, easy to read/write
+- **Append-friendly**: append new messages without rewriting the whole file
+- **Readable**: view directly with a text editor
+- **Fault-tolerant**: a corrupted line doesn’t break the whole file
+
+## “Memory” Implementation (Business-Layer Example)
+
+Below is a simple example that stores conversation history in JSONL files. This is just one possible approach; you can use databases, Redis, and other storage options based on your needs.
+
+### 1. Create a Store
+
+```go
+sessionDir := "./data/sessions"
+store, err := mem.NewStore(sessionDir)
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+### 2. Get or Create a Session
+
+```go
+sessionID := "083d16da-6b13-4fe6-afb0-c45d8f490ce1"
+session, err := store.GetOrCreate(sessionID)
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+### 3. Append a User Message
+
+```go
+userMsg := schema.UserMessage("hello")
+if err := session.Append(userMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+### 4. Get History and Call the Agent
+
+```go
+history := session.GetMessages()
+events := runner.Run(ctx, history)
+content := collectAssistantFromEvents(events)
+```
+
+### 5. Append an Assistant Message
+
+```go
+assistantMsg := schema.AssistantMessage(content, nil)
+if err := session.Append(assistantMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)):
+
+```go
+// Create or resume a session
+session, err := store.GetOrCreate(sessionID)
+if err != nil {
+ log.Fatal(err)
+}
+
+// User input
+userMsg := schema.UserMessage(line)
+if err := session.Append(userMsg); err != nil {
+ log.Fatal(err)
+}
+
+// Call the Agent
+history := session.GetMessages()
+events := runner.Run(ctx, history)
+content := collectAssistantFromEvents(events)
+
+// Save assistant reply
+assistantMsg := schema.AssistantMessage(content, nil)
+if err := session.Append(assistantMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+## Session vs Agent: How Business Layer and Framework Layer Work Together
+
+**Key takeaways:**
+
+- **Session is a business-layer concept**: implemented/managed by your code, responsible for storing/loading history
+- **Agent (Runner) is a framework-layer concept**: provided by Eino, responsible for processing messages and producing replies
+- **Integration point**: the business layer calls `session.GetMessages()` to obtain the message list, and passes it to `runner.Run(ctx, history)`
+
+**Layering:**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Business layer (your code) │
+│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
+│ │ Session │───→│ GetMessages() │───→│ runner.Run() │ │
+│ │ (storage) │ │ (message list)│ │ (framework) │ │
+│ └─────────────┘ └──────────────┘ └───────────────┘ │
+│ ↑ │ │
+│ │ ↓ │
+│ ┌─────────────┐ ┌───────────────┐ │
+│ │ Append() │←─────────────────────│ assistant reply│ │
+│ │ (persist) │ └───────────────┘ │
+│ └─────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ↓
+┌─────────────────────────────────────────────────────────────┐
+│ Framework layer (Eino) │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ adk.Runner: accepts message list, calls ChatModel, │ │
+│ │ and returns replies │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**Flow:**
+
+```
+┌─────────────────────────────────────────┐
+│ user input │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.Append() │
+ │ persist user message │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.GetMessages()│
+ │ get full history │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.Run(history) │
+ │ agent processes │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ collect reply │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.Append() │
+ │ persist assistant │
+ └──────────────────────┘
+```
+
+## Summary
+
+**Framework layer vs business layer:**
+
+- **Eino framework**: provides base abstractions like `adk.Runner` and `schema.Message`; it does not care how messages are stored
+- **Business layer (this chapter’s sample)**: Memory/Session/Store are application-level concepts managing storage of chat history
+
+**Business-layer concepts:**
+
+- **Memory**: persistent storage for history, supports cross-process recovery
+- **Session**: one complete conversation with ID, timestamps, message list
+- **Store**: manages multiple sessions (create/get/list/delete)
+- **JSONL**: a simple file format that is easy to append and inspect
+
+**How the two layers interact:**
+
+- business layer stores messages and calls `session.GetMessages()` to get the list
+- pass the list to `runner.Run(ctx, history)` for processing
+- collect replies from the framework and persist them back via the business layer
+
+> Tip: this chapter’s implementation is one simple example. In real projects, you can use databases, Redis, cloud storage, and implement additional capabilities like expiration cleanup, search, sharing, etc.
+
+## Further Thoughts: Choosing a Storage Solution
+
+The JSONL approach works for simple single-machine apps. In production you may need other options:
+
+**Other storage implementations:**
+
+- databases (MySQL, PostgreSQL, MongoDB)
+- Redis (distributed support)
+- cloud storage (S3, OSS)
+
+**Advanced features:**
+
+- session expiration cleanup
+- session search
+- session export/import
+- session sharing
diff --git a/content/en/docs/eino/quick_start/chapter_04_tool_and_filesystem.md b/content/en/docs/eino/quick_start/chapter_04_tool_and_filesystem.md
new file mode 100644
index 0000000000..b7df82472e
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_04_tool_and_filesystem.md
@@ -0,0 +1,329 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 4: Tools and Filesystem Access"
+weight: 4
+---
+
+Goal of this chapter: add Tool capability to the Agent so it can access the filesystem.
+
+## Why Tools
+
+In the first three chapters, our Agent could only chat and could not perform real operations.
+
+**Limitations of an Agent without tools:**
+
+- can only generate text responses
+- cannot access external resources (files, APIs, databases, etc.)
+- cannot execute real tasks (compute, query, modify, etc.)
+
+**What a Tool is for:**
+
+- **Tools extend an Agent’s capabilities**: enable the Agent to perform concrete operations
+- **Tools encapsulate implementation details**: the Agent cares only about inputs/outputs
+- **Tools are composable**: an Agent can have multiple Tools and call them as needed
+
+**Analogy:**
+
+- **Agent** = “an assistant” (can understand instructions, but needs tools to act)
+- **Tool** = “a toolbox” (file ops, network requests, DB queries, etc.)
+
+## Why Filesystem Access
+
+This example is “ChatWithDoc” (chat with docs). The goal is to help users learn Eino and write Eino code. So what is the best documentation?
+
+The answer: **the Eino repository code itself.**
+
+- **Code**: shows the real implementation
+- **Comments**: explain design rationale and usage details
+- **Examples**: demonstrate best practices
+
+With filesystem access, the Agent can read Eino source code, comments, and examples to provide accurate and up-to-date technical support.
+
+## Key Concepts
+
+### Tool Interfaces
+
+`Tool` is the interface family that defines executable capabilities in Eino:
+
+```go
+// BaseTool provides tool metadata. ChatModel uses it to decide whether and how to call the tool.
+type BaseTool interface {
+ Info(ctx context.Context) (*schema.ToolInfo, error)
+}
+
+// InvokableTool can be executed by ToolsNode.
+type InvokableTool interface {
+ BaseTool
+ // InvokableRun executes the tool. The argument is a JSON-encoded string and returns a string result.
+ InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
+}
+
+// StreamableTool is the streaming variant of InvokableTool.
+type StreamableTool interface {
+ BaseTool
+ // StreamableRun executes the tool in streaming mode and returns a StreamReader.
+ StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
+}
+```
+
+**Interface hierarchy:**
+
+- `BaseTool`: base interface, metadata only
+- `InvokableTool`: executable tool (extends BaseTool)
+- `StreamableTool`: streaming tool (extends BaseTool)
+
+### Backend Interface
+
+`Backend` is the abstraction used by Eino for filesystem operations:
+
+```go
+type Backend interface {
+ // List file info in a directory.
+ LsInfo(ctx context.Context, req *LsInfoRequest) ([]FileInfo, error)
+
+ // Read file content with optional line offset and limit.
+ Read(ctx context.Context, req *ReadRequest) (*FileContent, error)
+
+ // Search for matching content in files.
+ GrepRaw(ctx context.Context, req *GrepRequest) ([]GrepMatch, error)
+
+ // Match files by glob patterns.
+ GlobInfo(ctx context.Context, req *GlobInfoRequest) ([]FileInfo, error)
+
+ // Write file content.
+ Write(ctx context.Context, req *WriteRequest) error
+
+ // Edit file content (string replacements).
+ Edit(ctx context.Context, req *EditRequest) error
+}
+```
+
+### LocalBackend
+
+`LocalBackend` is a local filesystem implementation of Backend, directly accessing the OS filesystem:
+
+```go
+import localbk "github.com/cloudwego/eino-ext/adk/backend/local"
+
+backend, err := localbk.NewBackend(ctx, &localbk.Config{})
+```
+
+**Characteristics:**
+
+- direct access to local filesystem using Go standard library
+- supports all Backend methods
+- supports executing shell commands (ExecuteStreaming)
+- path safety: requires absolute paths to prevent directory traversal attacks
+- zero configuration: works out of the box
+
+## Implementation: Using DeepAgent
+
+This chapter uses the prebuilt DeepAgent. It exposes Backend and StreamingShell as top-level configuration, making it easy to register filesystem-related tools.
+
+### From ChatModelAgent to DeepAgent: When to Switch?
+
+In earlier chapters we used `ChatModelAgent`, which already supports multi-turn chat. To access the filesystem, we switch to `DeepAgent`.
+
+**ChatModelAgent vs DeepAgent:**
+
+
+| Capability | ChatModelAgent | DeepAgent |
+| Multi-turn chat | ✅ | ✅ |
+| Add custom tools | ✅ register each tool manually | ✅ register manually or auto-register |
+| Filesystem access (Backend) | ❌ build and register all file tools manually | ✅ top-level config, auto-register |
+| Command execution (StreamingShell) | ❌ build manually | ✅ top-level config, auto-register |
+| Built-in task management | ❌ | ✅ write_todos tool |
+| Sub-agent support | ❌ | ✅ |
+
+
+**Guidance:**
+
+- chat-only scenarios (no external access) → use `ChatModelAgent`
+- filesystem access or command execution → use `DeepAgent`
+
+### Why DeepAgent?
+
+Compared to using ChatModelAgent directly, DeepAgent provides:
+
+1. **Top-level configuration**: Backend and StreamingShell are passed directly
+2. **Auto tool registration**: configuring Backend auto-registers filesystem tools
+3. **Built-in task management**: the `write_todos` tool for planning and tracking
+4. **Sub-agent support**: configure specialized sub-agents for specific tasks
+5. **More power**: integrates filesystem, command execution, and more
+
+### Code
+
+```go
+import (
+ localbk "github.com/cloudwego/eino-ext/adk/backend/local"
+ "github.com/cloudwego/eino/adk/prebuilt/deep"
+)
+
+// Create LocalBackend.
+backend, err := localbk.NewBackend(ctx, &localbk.Config{})
+
+// Create DeepAgent and auto-register filesystem tools.
+agent, err := deep.New(ctx, &deep.Config{
+ Name: "Ch04ToolAgent",
+ Description: "ChatWithDoc agent with filesystem access via LocalBackend.",
+ ChatModel: cm,
+ Instruction: instruction,
+ Backend: backend, // filesystem operations
+ StreamingShell: backend, // command execution
+ MaxIteration: 50,
+})
+```
+
+### Tools Auto-Registered by DeepAgent
+
+When `Backend` and `StreamingShell` are configured, DeepAgent automatically registers:
+
+- `read_file`: read file content
+- `write_file`: write file content
+- `edit_file`: edit file content
+- `glob`: find files by glob pattern
+- `grep`: search for content in files
+- `execute`: run shell commands
+
+## Code Location
+
+- Entry code: [cmd/ch04/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch04/main.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
+
+This chapter also uses `PROJECT_ROOT` (optional; see below).
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# Optional: set the root directory of the Eino core repo.
+# If unset, the Agent uses the current working directory (the chatwitheino directory) as root.
+# To let the Agent search the full Eino codebase, point this to the Eino core repo root.
+export PROJECT_ROOT=/path/to/eino
+
+# Verify the path (you should see adk/, components/, compose/, etc.)
+ls $PROJECT_ROOT
+
+go run ./cmd/ch04
+```
+
+**About PROJECT_ROOT:**
+
+- **Unset**: defaults to current working directory (the `chatwitheino` directory). The Agent can only access files in this example project; good enough for a quick try.
+- **Set**: point to the Eino core repo root. The Agent can search the full Eino codebase (core, extensions, examples). This is the full ChatWithEino scenario.
+
+**Recommended three-repo layout (for the full experience):**
+
+```
+eino/ # PROJECT_ROOT (Eino core repo)
+├── adk/
+├── components/
+├── compose/
+├── ext/ # eino-ext (extensions: OpenAI, Ark, etc.)
+├── examples/ # eino-examples (this repo, where this sample lives)
+│ └── quickstart/
+│ └── chatwitheino/
+└── ...
+```
+
+You can use `dev_setup.sh` to set up this layout automatically:
+
+```bash
+# Run in the eino root to clone the extension repo and examples repo into the right locations.
+bash scripts/dev_setup.sh
+```
+
+Example output:
+
+```
+you> List files in the current directory
+[assistant] Sure, let me list the files...
+[tool call] glob(pattern: "*")
+[tool result] Found 5 files:
+- main.go
+- go.mod
+- go.sum
+- README.md
+- cmd/
+
+you> Read the contents of main.go
+[assistant] Sure, let me read main.go...
+[tool call] read_file(file_path: "main.go")
+[tool result] File content:
+...
+```
+
+Note: if you encounter Tool errors and the Agent gets interrupted, don’t panic—this is normal. Tool failures are common (bad parameters, missing files, etc.). We’ll discuss graceful Tool error handling in the next chapter.
+
+## Tool Call Flow
+
+When the Agent needs to call a tool:
+
+```
+┌─────────────────────────────────────────┐
+│ user: list files in current directory │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent analyzes intent │
+ │ chooses glob tool │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ create Tool Call │
+ │ {"pattern": "*"} │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ execute Tool │
+ │ glob("*") │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ return Tool Result │
+ │ {"files": [...]} │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent responds │
+ │ "found 5 files..." │
+ └──────────────────────┘
+```
+
+## Summary
+
+- **Tool**: extends an Agent so it can perform concrete operations
+- **Backend**: an abstraction for filesystem operations
+- **LocalBackend**: local filesystem implementation of Backend
+- **DeepAgent**: a prebuilt advanced Agent with top-level Backend/StreamingShell config
+- **Auto tool registration**: configuring Backend auto-registers filesystem tools
+- **Tool call flow**: intent → tool call → execute → result → reply
+
+## Further Thoughts
+
+**Other tool types:**
+
+- HTTP tools: call external APIs
+- database tools: query databases
+- calculator tools: perform computations
+- code executor tools: run code
+
+**Other Backend implementations:**
+
+- implement alternative storage backends based on the Backend interface
+- e.g. cloud storage, database storage
+- LocalBackend already provides a complete local filesystem backend
+
+**Creating custom tools:**
+
+If you need custom tools, you can use `utils.InferTool` to infer a Tool from a function. See:
+
+- [Tool interface docs](https://github.com/cloudwego/eino/tree/main/components/tool)
+- [Tool creation examples](https://github.com/cloudwego/eino-examples/tree/main/components/tool)
diff --git a/content/en/docs/eino/quick_start/chapter_05_middleware.md b/content/en/docs/eino/quick_start/chapter_05_middleware.md
new file mode 100644
index 0000000000..63b39de674
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_05_middleware.md
@@ -0,0 +1,447 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 5: Middleware (The Middleware Pattern)"
+weight: 5
+---
+
+Goal of this chapter: understand the Middleware pattern and implement Tool error handling and a ChatModel retry mechanism.
+
+## Why Middleware
+
+In Chapter 4 we added Tool capability so the Agent can access the filesystem. In real applications, **Tool failures and ChatModel failures are common**, for example:
+
+- **Tool errors**: missing files, invalid arguments, insufficient permissions, etc.
+- **ChatModel errors**: rate limiting (429), network timeouts, service unavailable, etc.
+
+### Problem 1: Tool Errors Can Break the Whole Flow
+
+When a Tool execution fails, the error can propagate to the Agent and interrupt the conversation:
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+Error: open nonexistent.txt: no such file or directory
+// conversation interrupted; the user has to restart
+```
+
+### Problem 2: Model Calls Can Fail Due to Rate Limits
+
+When the model API returns 429 (Too Many Requests), the conversation also gets interrupted:
+
+```
+Error: rate limit exceeded (429)
+// conversation interrupted
+```
+
+### Desired Behavior
+
+Often we **don’t want these errors to terminate the Agent flow**. Instead, we want to pass the error information back to the model so it can self-correct and continue, for example:
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] Sorry, the file doesn't exist. Let me list the current directory first...
+[tool call] glob(pattern: "*")
+```
+
+### What Middleware Is For
+
+The **Middleware pattern** can extend the behavior of Tools and ChatModels, which fits this problem well:
+
+- **Middleware is an interceptor for the Agent**: insert custom logic before/after calls
+- **Middleware can handle errors**: convert errors into model-readable formats
+- **Middleware can implement retries**: automatically retry failed operations
+- **Middleware is composable**: multiple middlewares can be chained
+
+**Analogy:**
+
+- **Agent** = “business logic”
+- **Middleware** = “AOP aspects” (logging, retries, error handling, and other cross-cutting concerns)
+
+## Code Location
+
+- Entry code: [cmd/ch05/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch05/main.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark). Also, set `PROJECT_ROOT` as in Chapter 4:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino core repo root
+```
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# set project root
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch05
+```
+
+Example output:
+
+```
+you> List files in the current directory
+[assistant] Sure, let me list the files...
+[tool call] list_files(directory: ".")
+
+you> Read a file that does not exist
+[assistant] Trying to read the file...
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] Sorry, the file doesn't exist...
+```
+
+## Key Concepts
+
+### Middleware Interface
+
+`ChatModelAgentMiddleware` is the middleware interface for agents:
+
+```go
+type ChatModelAgentMiddleware interface {
+ // BeforeAgent is called before each agent run, allowing modification of
+ // the agent's instruction and tools configuration.
+ BeforeAgent(ctx context.Context, runCtx *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error)
+
+ // BeforeModelRewriteState is called before each model invocation.
+ // The returned state is persisted to the agent's internal state and passed to the model.
+ BeforeModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
+
+ // AfterModelRewriteState is called after each model invocation.
+ // The input state includes the model's response as the last message.
+ AfterModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
+
+ // WrapInvokableToolCall wraps a tool's synchronous execution with custom behavior.
+ // This method is only called for tools that implement InvokableTool.
+ WrapInvokableToolCall(ctx context.Context, endpoint InvokableToolCallEndpoint, tCtx *ToolContext) (InvokableToolCallEndpoint, error)
+
+ // WrapStreamableToolCall wraps a tool's streaming execution with custom behavior.
+ // This method is only called for tools that implement StreamableTool.
+ WrapStreamableToolCall(ctx context.Context, endpoint StreamableToolCallEndpoint, tCtx *ToolContext) (StreamableToolCallEndpoint, error)
+
+ // WrapEnhancedInvokableToolCall wraps an enhanced tool's synchronous execution.
+ // This method is only called for tools that implement EnhancedInvokableTool.
+ WrapEnhancedInvokableToolCall(ctx context.Context, endpoint EnhancedInvokableToolCallEndpoint, tCtx *ToolContext) (EnhancedInvokableToolCallEndpoint, error)
+
+ // WrapEnhancedStreamableToolCall wraps an enhanced tool's streaming execution.
+ // This method is only called for tools that implement EnhancedStreamableTool.
+ WrapEnhancedStreamableToolCall(ctx context.Context, endpoint EnhancedStreamableToolCallEndpoint, tCtx *ToolContext) (EnhancedStreamableToolCallEndpoint, error)
+
+ // WrapModel wraps a chat model with custom behavior.
+ // This method is called at request time when the model is about to be invoked.
+ WrapModel(ctx context.Context, m model.BaseChatModel, mc *ModelContext) (model.BaseChatModel, error)
+}
+```
+
+**Design ideas:**
+
+- **Decorator pattern**: each middleware wraps the original call and can modify input/output/errors
+- **Onion model**: requests go from outer to inner; responses bubble back from inner to outer
+- **Composable**: multiple middlewares run in order
+
+### Middleware Execution Order
+
+`Handlers` (middlewares) wrap in **array order**, forming an onion:
+
+```go
+Handlers: []adk.ChatModelAgentMiddleware{
+ &middlewareA{}, // outermost: wraps first; intercepts requests first
+ &middlewareB{}, // middle
+ &middlewareC{}, // innermost: wraps last
+}
+```
+
+**For Tool calls:**
+
+```
+request → A.Wrap → B.Wrap → C.Wrap → actual Tool execution → C returns → B returns → A returns → response
+```
+
+Practical tip: put `safeToolMiddleware` (error capture) as the innermost layer (at the end of the array), so interrupt errors thrown by other middlewares can propagate outward correctly.
+
+### SafeToolMiddleware
+
+`SafeToolMiddleware` converts Tool errors into strings so the model can understand and handle them:
+
+```go
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ // Convert errors to strings instead of returning an error.
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+```
+
+**Effect:**
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] Sorry, the file doesn't exist. Please check the path...
+// conversation continues; the model can adapt based on the error message
+```
+
+### ModelRetryConfig
+
+`ModelRetryConfig` configures automatic retries for ChatModel:
+
+```go
+type ModelRetryConfig struct {
+ MaxRetries int // max retry count
+ IsRetryAble func(ctx context.Context, err error) bool // whether an error is retryable
+}
+```
+
+**Usage (DeepAgent example):**
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ // ...
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ // 429 rate limit errors are retryable
+ return strings.Contains(err.Error(), "429") ||
+ strings.Contains(err.Error(), "Too Many Requests") ||
+ strings.Contains(err.Error(), "qpm limit")
+ },
+ },
+})
+```
+
+**Retry strategy:**
+
+- exponential backoff: retry interval increases each time
+- configurable conditions: `IsRetryAble` decides what to retry
+- automatic recovery: no user intervention needed
+
+## Implementing the Middleware
+
+### 1. Implement SafeToolMiddleware
+
+```go
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ // Interrupt errors must not be converted; they must keep propagating.
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return "", err
+ }
+ // Convert other errors to strings.
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+```
+
+### 2. Handle Streaming Tool Errors
+
+```go
+func (m *safeToolMiddleware) WrapStreamableToolCall(
+ _ context.Context,
+ endpoint adk.StreamableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.StreamableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (*schema.StreamReader[string], error) {
+ sr, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return nil, err
+ }
+ // Return a single-chunk stream containing the error message.
+ return singleChunkReader(fmt.Sprintf("[tool error] %v", err)), nil
+ }
+ // Wrap the stream and catch errors inside the stream.
+ return safeWrapReader(sr), nil
+ }, nil
+}
+```
+
+### 3. Configure the Agent to Use Middleware
+
+This chapter continues using `DeepAgent` from Chapter 4. Register middlewares in the `Handlers` field:
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ Name: "Ch05MiddlewareAgent",
+ Description: "ChatWithDoc agent with safe tool middleware and retry.",
+ ChatModel: cm,
+ Instruction: agentInstruction,
+ Backend: backend,
+ StreamingShell: backend,
+ MaxIteration: 50,
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &safeToolMiddleware{}, // convert Tool errors to strings
+ },
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ return strings.Contains(err.Error(), "429") ||
+ strings.Contains(err.Error(), "Too Many Requests")
+ },
+ },
+})
+```
+
+Note: the `Handlers` field (config) and the “Middleware” concept discussed in this chapter refer to the same thing. `Handlers` is the config field name; `ChatModelAgentMiddleware` is the interface name.
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [cmd/ch05/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch05/main.go)):
+
+```go
+// SafeToolMiddleware catches Tool errors and converts them to strings.
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return "", err
+ }
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+
+// Configure DeepAgent (same as Chapter 4, plus Handlers and ModelRetryConfig).
+agent, _ := deep.New(ctx, &deep.Config{
+ ChatModel: cm,
+ Backend: backend,
+ StreamingShell: backend,
+ MaxIteration: 50,
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &safeToolMiddleware{},
+ },
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ return strings.Contains(err.Error(), "429")
+ },
+ },
+})
+```
+
+## Middleware Execution Flow
+
+```
+┌─────────────────────────────────────────┐
+│ user: read a nonexistent file │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent analyzes intent │
+ │ chooses read_file │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ SafeToolMiddleware │
+ │ intercepts Tool call │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ execute read_file │
+ │ returns an error │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ SafeToolMiddleware │
+ │ converts error to str│
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Tool Result │
+ │ "[tool error] ..." │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent responds │
+ │ "sorry..." │
+ └──────────────────────┘
+```
+
+## Summary
+
+- **Middleware**: interceptors for Agents, inserting custom logic around calls
+- **SafeToolMiddleware**: converts Tool errors into strings so the model can handle them
+- **ModelRetryConfig**: automatic ChatModel retry to handle transient errors like rate limits
+- **Decorator pattern**: middleware wraps calls and can modify inputs/outputs/errors
+- **Onion model**: requests go inward; responses go outward
+
+## Further Thoughts
+
+**Built-in Eino middlewares:**
+
+
+| Middleware | Description |
+| reduction | Tool output reduction: truncates overly long tool outputs and offloads them to filesystem to prevent context overflow |
+| summarization | Automatic chat-history summarization when token count exceeds a threshold |
+| skill | Skill loader: dynamically loads and executes predefined skills |
+
+
+**Example middleware chain:**
+
+```go
+import (
+ "github.com/cloudwego/eino/adk/middlewares/reduction"
+ "github.com/cloudwego/eino/adk/middlewares/summarization"
+ "github.com/cloudwego/eino/adk/middlewares/skill"
+)
+
+// Create reduction middleware: manage tool output length.
+reductionMW, _ := reduction.New(ctx, &reduction.Config{
+ Backend: filesystemBackend, // storage backend
+ MaxLengthForTrunc: 50000, // max output length per tool call
+ MaxTokensForClear: 30000, // token threshold for cleanup
+})
+
+// Create summarization middleware: compress chat history automatically.
+summarizationMW, _ := summarization.New(ctx, &summarization.Config{
+ Model: chatModel, // model used to generate summaries
+ Trigger: &summarization.TriggerCondition{
+ ContextTokens: 190000, // token threshold for summarization
+ },
+})
+
+// Combine middlewares (conceptual example; with DeepAgent replace adk.NewChatModelAgent with deep.New).
+agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
+ Handlers: []adk.ChatModelAgentMiddleware{ // config field name is Handlers; conceptually equals “middlewares”
+ summarizationMW, // outermost: chat-history summarization
+ reductionMW, // middle: tool-output reduction
+ },
+})
+```
diff --git a/content/en/docs/eino/quick_start/chapter_06_callback_and_trace.md b/content/en/docs/eino/quick_start/chapter_06_callback_and_trace.md
new file mode 100644
index 0000000000..9e7f961d61
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_06_callback_and_trace.md
@@ -0,0 +1,367 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 6: Callback and Trace (Observability)"
+weight: 6
+---
+
+Goal of this chapter: understand the Callback mechanism and integrate CozeLoop for tracing and observability.
+
+## Code Location
+
+- Entry code: [cmd/ch06/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch06/main.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark). Also set `PROJECT_ROOT` as in Chapter 4:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino core repo root (defaults to current directory if unset)
+```
+
+Optional: configure CozeLoop for tracing:
+
+```bash
+export COZELOOP_WORKSPACE_ID=your_workspace_id
+export COZELOOP_API_TOKEN=your_token
+```
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# set project root
+export PROJECT_ROOT=/path/to/your/project
+
+# optional: configure CozeLoop
+export COZELOOP_WORKSPACE_ID=your_workspace_id
+export COZELOOP_API_TOKEN=your_token
+
+go run ./cmd/ch06
+```
+
+Example output:
+
+```
+[trace] starting session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+you> hi
+[trace] chat_model_generate: model=gpt-4.1-mini tokens=150
+[trace] tool_call: name=list_files duration=23ms
+[assistant] Hi! How can I help you today?
+```
+
+## From Black Box to White Box: Why Callbacks
+
+In the previous chapters, our Agent behaved like a “black box”: we provide a question and get an answer, but we don’t know what happened inside.
+
+**Problems with a black box:**
+
+- how many times the model was called
+- how long Tools took to run
+- how many tokens were consumed
+- hard to locate root causes when something goes wrong
+
+**What Callbacks are for:**
+
+- **Callbacks are Eino’s side-channel mechanism**: consistent across component → compose → adk
+- **Callbacks fire at fixed points**: five key lifecycle moments
+- **Callbacks can extract runtime information**: input, output, errors, streaming data, etc.
+- **Callbacks are broadly useful**: observability, logging, metrics, tracing, debugging, auditing, etc.
+
+**Analogy:**
+
+- **Agent** = “main path / business logic”
+- **Callback** = “side-channel hooks” (extract information at fixed points)
+
+## Key Concepts
+
+### Handler Interface
+
+`Handler` is the core callback-handler interface in Eino:
+
+```go
+type Handler interface {
+ // Non-streaming input (before component starts processing).
+ OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context
+
+ // Non-streaming output (after component successfully returns).
+ OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context
+
+ // Error (when component returns an error).
+ OnError(ctx context.Context, info *RunInfo, err error) context.Context
+
+ // Streaming input (when component receives streaming input).
+ OnStartWithStreamInput(ctx context.Context, info *RunInfo,
+ input *schema.StreamReader[CallbackInput]) context.Context
+
+ // Streaming output (when component returns streaming output).
+ OnEndWithStreamOutput(ctx context.Context, info *RunInfo,
+ output *schema.StreamReader[CallbackOutput]) context.Context
+}
+```
+
+**Design ideas:**
+
+- **Side-channel**: does not interfere with the main flow; extracts information at fixed points
+- **End-to-end coverage**: supported across component → compose → adk
+- **State passing**: a Handler can pass state from OnStart → OnEnd via context
+- **Performance optimization**: implement `TimingChecker` to skip timings you don’t need
+
+**RunInfo:**
+
+```go
+type RunInfo struct {
+ Name string // business name (node name or user-provided)
+ Type string // implementation type (e.g. "OpenAI")
+ Component string // component type (e.g. "ChatModel")
+}
+```
+
+**Important notes:**
+
+- streaming callbacks must close StreamReaders, otherwise goroutines may leak
+- do not mutate Input/Output: they may be shared by downstream consumers
+- RunInfo may be nil; check before use
+
+### CozeLoop
+
+CozeLoop is an open-source AI observability platform that provides:
+
+- **Tracing**: visualize the full call chain
+- **Metrics**: latency, token consumption, error rates, etc.
+- **Log aggregation**: centralized log management
+- **Debugging**: online inspection and debugging
+
+**Integration:**
+
+```go
+import (
+ clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"
+ "github.com/cloudwego/eino/callbacks"
+ "github.com/coze-dev/cozeloop-go"
+)
+
+// Create a CozeLoop client.
+client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(apiToken),
+ cozeloop.WithWorkspaceID(workspaceID),
+)
+
+// Register as a global callback.
+callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+```
+
+### Callback Timings
+
+Callbacks fire at five key lifecycle moments. `Timing*` are Eino internal constant names (used by `TimingChecker`). The corresponding Handler methods are shown on the right:
+
+
+| Timing constant | Handler method | Trigger point | Input/Output |
+TimingOnStart | OnStart | before component processing | CallbackInput |
+TimingOnEnd | OnEnd | after successful return | CallbackOutput |
+TimingOnError | OnError | on error return | error |
+TimingOnStartWithStreamInput | OnStartWithStreamInput | on streaming input | StreamReader[CallbackInput] |
+TimingOnEndWithStreamOutput | OnEndWithStreamOutput | on streaming output | StreamReader[CallbackOutput] |
+
+
+**Example: ChatModel.Generate**
+
+```
+┌─────────────────────────────────────────┐
+│ ChatModel.Generate(ctx, messages) │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnStart │ ← input: CallbackInput (messages)
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ model processing │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnEnd │ ← output: CallbackOutput (response)
+ └──────────────────────┘
+```
+
+**Example: streaming output**
+
+```
+┌─────────────────────────────────────────┐
+│ ChatModel.Stream(ctx, messages) │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnStart │ ← input: CallbackInput (messages)
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ model processing │ (streaming)
+ └──────────────────────┘
+ ↓
+ ┌────────────────────────┐
+ │ OnEndWithStreamOutput │ ← output: StreamReader[CallbackOutput]
+ └────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ chunks returned │
+ └──────────────────────┘
+```
+
+Notes:
+
+- streaming errors (mid-stream) do not trigger OnError; they are returned through StreamReader
+- a Handler can pass state from OnStart → OnEnd via context
+- there is no guaranteed execution order among different handlers
+
+## Implementing Callbacks
+
+### 1. Implement a Custom Callback Handler
+
+Fully implementing `Handler` requires all five methods. Eino provides `callbacks.HandlerHelper` to simplify:
+
+```go
+import "github.com/cloudwego/eino/callbacks"
+
+// Register the timings you care about via NewHandlerHelper.
+handler := callbacks.NewHandlerHelper().
+ OnStart(func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
+ log.Printf("[trace] %s/%s start", info.Component, info.Name)
+ return ctx
+ }).
+ OnEnd(func(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {
+ log.Printf("[trace] %s/%s end", info.Component, info.Name)
+ return ctx
+ }).
+ OnError(func(ctx context.Context, info *callbacks.RunInfo, err error) context.Context {
+ log.Printf("[trace] %s/%s error: %v", info.Component, info.Name, err)
+ return ctx
+ }).
+ Handler()
+
+// Register as a global callback.
+callbacks.AppendGlobalHandlers(handler)
+```
+
+Note: `RunInfo` may be nil (e.g., top-level calls). Check before use.
+
+### 2. Integrate CozeLoop
+
+```go
+func setupCozeLoop(ctx context.Context) (*cozeloop.Client, error) {
+ apiToken := os.Getenv("COZELOOP_API_TOKEN")
+ workspaceID := os.Getenv("COZELOOP_WORKSPACE_ID")
+
+ if apiToken == "" || workspaceID == "" {
+ return nil, nil // skip if not configured
+ }
+
+ client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(apiToken),
+ cozeloop.WithWorkspaceID(workspaceID),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // Register as a global callback.
+ callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+
+ return client, nil
+}
+```
+
+### 3. Use in main
+
+```go
+func main() {
+ ctx := context.Background()
+
+ // Setup CozeLoop (optional).
+ client, err := setupCozeLoop(ctx)
+ if err != nil {
+ log.Printf("cozeloop setup failed: %v", err)
+ }
+ if client != nil {
+ defer func() {
+ time.Sleep(5 * time.Second) // wait for reporting
+ client.Close(ctx)
+ }()
+ }
+
+ // Create agent and run...
+}
+```
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [cmd/ch06/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch06/main.go)):
+
+```go
+// Setup CozeLoop tracing.
+cozeloopApiToken := os.Getenv("COZELOOP_API_TOKEN")
+cozeloopWorkspaceID := os.Getenv("COZELOOP_WORKSPACE_ID")
+if cozeloopApiToken != "" && cozeloopWorkspaceID != "" {
+ client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(cozeloopApiToken),
+ cozeloop.WithWorkspaceID(cozeloopWorkspaceID),
+ )
+ if err != nil {
+ log.Fatalf("cozeloop.NewClient failed: %v", err)
+ }
+ defer func() {
+ time.Sleep(5 * time.Second)
+ client.Close(ctx)
+ }()
+ callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+}
+```
+
+## Value of Observability
+
+### 1. Performance Analysis
+
+With data collected via callbacks, you can analyze:
+
+- distribution of model call latency
+- top tool execution times
+- token consumption trends
+
+### 2. Error Tracing
+
+When something goes wrong:
+
+- inspect the full call chain
+- locate where the failure occurred
+- analyze root causes
+
+### 3. Cost Optimization
+
+With token consumption data, you can:
+
+- identify high-cost conversations
+- optimize prompts to reduce tokens
+- choose more cost-effective models
+
+## Summary
+
+- **Callback**: Eino’s observability hooks triggered at key points
+- **CozeLoop**: an AI observability platform
+- **Global registration**: register global callbacks via `callbacks.AppendGlobalHandlers`
+- **Non-invasive**: business code doesn’t need to change; callbacks fire automatically
+- **Observability value**: performance, error tracing, and cost optimization
+
+## Further Thoughts
+
+**Other callback implementations:**
+
+- OpenTelemetry callback: integrate with a standard observability protocol
+- custom logging callback: write logs to local files
+- metrics callback: integrate with Prometheus and other monitoring systems
+
+**Advanced usage:**
+
+- sampling (record only a subset of requests)
+- rate limiting based on token consumption
+- alerting when error rates are high
diff --git a/content/en/docs/eino/quick_start/chapter_07_interrupt_resume.md b/content/en/docs/eino/quick_start/chapter_07_interrupt_resume.md
new file mode 100644
index 0000000000..a9015ddee9
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_07_interrupt_resume.md
@@ -0,0 +1,355 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 7: Interrupt/Resume (Human-in-the-Loop)"
+weight: 7
+---
+
+Goal of this chapter: understand the Interrupt/Resume mechanism and implement a Tool approval workflow so users can confirm before sensitive operations.
+
+## Code Location
+
+- Entry code: [cmd/ch07/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch07/main.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark). Also set `PROJECT_ROOT` as in Chapter 4:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino core repo root (defaults to current directory if unset)
+```
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# set project root
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch07
+```
+
+Example output:
+
+```
+you> Please run: echo hello
+
+⚠️ Approval Required ⚠️
+Tool: execute
+Arguments: {"command":"echo hello"}
+
+Approve this action? (y/n): y
+[tool result] hello
+
+hello
+```
+
+## From Auto-Execution to Human Approval: Why Interrupt
+
+In previous chapters, the Agent automatically executed all Tool calls. In some scenarios this is dangerous:
+
+**Risks of auto-execution:**
+
+- deleting files: accidental data loss
+- sending emails: wrong content or recipients
+- executing commands: dangerous operations
+- modifying configs: breaking system settings
+
+**What Interrupt is for:**
+
+- **Interrupt pauses the Agent**: stop before critical operations and wait for user confirmation
+- **Interrupt can carry information**: show users what will be executed
+- **Interrupt is resumable**: continue after approval; return a rejection message if disapproved
+
+**Analogy:**
+
+- **auto-execution** = “autopilot”
+- **Interrupt** = “human takeover” for critical decisions
+
+## Key Concepts
+
+### Interrupt Mechanism
+
+`Interrupt` is Eino’s core mechanism for human-in-the-loop collaboration.
+
+Core idea: **pause before a sensitive action, then continue after user confirmation.**
+
+An approval-required tool call is split into **two phases**:
+
+1. **First call (trigger interrupt)**: the Tool stores the current arguments, then returns an interrupt signal. Runner pauses execution and emits an Interrupt event.
+2. **Resume after approval**: Runner calls the Tool again. The Tool detects it is in a resumed run, reads the user’s approval result, and executes (or rejects).
+
+**Simplified pseudocode:**
+
+```
+func myTool(ctx, args):
+ if first call:
+ store args
+ return interrupt signal // Runner pauses and shows an approval prompt
+ else: // second call after Resume
+ if approved:
+ return doOperation(stored args)
+ else:
+ return "Operation rejected by user"
+```
+
+**Full example with key fields:**
+
+```go
+// Trigger interrupt in a Tool.
+func myTool(ctx context.Context, args string) (string, error) {
+ // wasInterrupted: whether this is the second call after Resume (false on first call)
+ // storedArgs: arguments saved via StatefulInterrupt during the first call; available after Resume
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+
+ if !wasInterrupted {
+ // First call: trigger interrupt and save args for Resume.
+ return "", tool.StatefulInterrupt(ctx, &ApprovalInfo{
+ ToolName: "my_tool",
+ ArgumentsInJSON: args,
+ }, args) // the third argument is saved state (retrieved later via storedArgs)
+ }
+
+ // Second call after Resume: read user approval result.
+ // isTarget: whether this Resume targets the current Tool (each Resume targets one Tool)
+ // hasData: whether Resume carries approval result data
+ // data: approval result provided by user
+ isTarget, hasData, data := tool.GetResumeContext[*ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return doSomething(storedArgs) // execute with saved args
+ }
+ return "Operation rejected by user", nil
+ }
+
+ // Other cases (isTarget=false means this Resume is for a different Tool): interrupt again.
+ return "", tool.StatefulInterrupt(ctx, &ApprovalInfo{
+ ToolName: "my_tool",
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+}
+```
+
+### ApprovalMiddleware
+
+`ApprovalMiddleware` is a reusable approval middleware that intercepts specific Tool calls:
+
+```go
+type approvalMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *approvalMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ tCtx *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ // Only intercept tools that require approval.
+ if tCtx.Name != "execute" {
+ return endpoint, nil
+ }
+
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+
+ if !wasInterrupted {
+ return "", tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: args,
+ }, args)
+ }
+
+ isTarget, hasData, data := tool.GetResumeContext[*commontool.ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return endpoint(ctx, storedArgs, opts...)
+ }
+ if data.DisapproveReason != nil {
+ return fmt.Sprintf("tool '%s' disapproved: %s", tCtx.Name, *data.DisapproveReason), nil
+ }
+ return fmt.Sprintf("tool '%s' disapproved", tCtx.Name), nil
+ }
+
+ // Interrupt again.
+ return "", tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+ }, nil
+}
+
+func (m *approvalMiddleware) WrapStreamableToolCall(
+ _ context.Context,
+ endpoint adk.StreamableToolCallEndpoint,
+ tCtx *adk.ToolContext,
+) (adk.StreamableToolCallEndpoint, error) {
+ // If the agent configures StreamingShell, execute uses streaming; implement this method to intercept it.
+ if tCtx.Name != "execute" {
+ return endpoint, nil
+ }
+ return func(ctx context.Context, args string, opts ...tool.Option) (*schema.StreamReader[string], error) {
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+ if !wasInterrupted {
+ return nil, tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: args,
+ }, args)
+ }
+
+ isTarget, hasData, data := tool.GetResumeContext[*commontool.ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return endpoint(ctx, storedArgs, opts...)
+ }
+ if data.DisapproveReason != nil {
+ return singleChunkReader(fmt.Sprintf("tool '%s' disapproved: %s", tCtx.Name, *data.DisapproveReason)), nil
+ }
+ return singleChunkReader(fmt.Sprintf("tool '%s' disapproved", tCtx.Name)), nil
+ }
+
+ isTarget, _, _ = tool.GetResumeContext[any](ctx)
+ if !isTarget {
+ return nil, tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+ }
+
+ return endpoint(ctx, storedArgs, opts...)
+ }, nil
+}
+```
+
+### CheckPointStore
+
+`CheckPointStore` is the key component for interrupt/resume:
+
+```go
+type CheckPointStore interface {
+ // Persist a checkpoint.
+ Put(ctx context.Context, key string, checkpoint *Checkpoint) error
+
+ // Load a checkpoint.
+ Get(ctx context.Context, key string) (*Checkpoint, error)
+}
+```
+
+**Why CheckPointStore?**
+
+- persist state during interrupts: Tool args, execution position, etc.
+- load state on resume: continue from the interruption point
+- support cross-process recovery: resume after process restart
+
+## Implementing Interrupt/Resume
+
+### 1. Configure Runner with CheckPointStore
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+ CheckPointStore: adkstore.NewInMemoryStore(), // in-memory store
+})
+```
+
+### 2. Configure the Agent with ApprovalMiddleware
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ // ... other config
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &approvalMiddleware{}, // add approval middleware
+ &safeToolMiddleware{}, // convert Tool errors to strings (interrupt errors still propagate)
+ },
+})
+```
+
+### 3. Handle Interrupt Events
+
+```go
+checkPointID := sessionID
+
+events := runner.Run(ctx, history, adk.WithCheckPointID(checkPointID))
+content, interruptInfo, err := printAndCollectAssistantFromEvents(events)
+if err != nil {
+ return err
+}
+
+if interruptInfo != nil {
+ // Tip: use the same stdin reader for both "user input" and "approval y/n",
+ // to avoid treating the approval input as the next `you>` message.
+ content, err = handleInterrupt(ctx, runner, checkPointID, interruptInfo, reader)
+ if err != nil {
+ return err
+ }
+}
+
+_ = session.Append(schema.AssistantMessage(content, nil))
+```
+
+## Interrupt/Resume Execution Flow
+
+```
+┌─────────────────────────────────────────┐
+│ user: run echo hello │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent analyzes intent │
+ │ chooses execute │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ ApprovalMiddleware │
+ │ intercepts Tool call │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ trigger Interrupt │
+ │ store state to Store │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ return Interrupt event│
+ │ wait for approval │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ user inputs y/n │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.ResumeWith... │
+ │ resume execution │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ execute execute │
+ │ or return rejection │
+ └──────────────────────┘
+```
+
+## Summary
+
+- **Interrupt**: pauses before critical operations and waits for confirmation
+- **Resume**: resumes execution after approval, or returns a rejection message
+- **ApprovalMiddleware**: reusable approval middleware intercepting specific Tool calls
+- **CheckPointStore**: persists interrupt state for cross-process recovery
+- **Human-in-the-loop**: humans confirm critical decisions to improve safety
+
+## Further Thoughts
+
+**Other Interrupt scenarios:**
+
+- multi-choice approval: user chooses one option
+- parameter completion: user provides missing parameters
+- conditional branching: user decides execution path
+
+**Approval strategies:**
+
+- whitelist: approve only sensitive operations
+- blacklist: approve everything except safe operations
+- dynamic rules: decide based on argument content
diff --git a/content/en/docs/eino/quick_start/chapter_08_graph_tool.md b/content/en/docs/eino/quick_start/chapter_08_graph_tool.md
new file mode 100644
index 0000000000..be990ea582
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_08_graph_tool.md
@@ -0,0 +1,313 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 8: Graph Tool (Complex Workflows)"
+weight: 8
+---
+
+Goal of this chapter: understand the concept of Graph Tools, implement parallel chunk retrieval for large files, and introduce the `compose` package to build complex workflows.
+
+## Code Location
+
+- Entry code: [cmd/ch08/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch08/main.go)
+- RAG implementation: [rag/rag.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/rag/rag.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+# set project root
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch08
+```
+
+Example output:
+
+```
+you> Please analyze the WebSocket handshake section in RFC6455
+[assistant] Sure, let me analyze the document...
+[tool call] answer_from_document(file_path: "rfc6455.txt", question: "WebSocket handshake")
+[tool result] Found 3 relevant chunks; generating an answer...
+[assistant] According to RFC6455, the WebSocket handshake works as follows...
+```
+
+## From Simple Tools to Graph Tools: Why Complex Workflows
+
+In Chapter 4 we created simple tools where each Tool does a single task. In real scenarios, many tasks require multiple steps working together.
+
+**Limitations of simple tools:**
+
+- single responsibility: each Tool does only one thing
+- no parallelism: independent tasks cannot run concurrently
+- hard to reuse: complex logic is difficult to decompose and recombine
+
+Important note: this chapter shows only a small slice of the compose/graph/workflow capabilities.
+
+From a broader view, Eino’s `compose` package provides a general, deterministic orchestration capability: you can organize any “deterministic business process” into executable pipelines via Graph/Chain/Workflow, and it can **natively orchestrate all Eino components** (ChatModel, Prompt, Tools, Retriever, Embedding, Indexer, etc.), with full **callback** support and **interrupt/resume + checkpoint** support.
+
+**What a Graph Tool is for:**
+
+- **Graph Tool is a Tool wrapper around compose workflows**: wrap compiled `compose.Graph / compose.Chain / compose.Workflow` into a Tool that an Agent can call
+- **Parallelism/branching/composition**: provided by compose (parallel, branches, field mapping, sub-graphs, etc.); Graph Tool just exposes an entry point
+- **State management and persistence**: pass data between nodes, and save/restore run state via checkpoints
+- **Interrupt/resume**: both inside the workflow (interrupt triggered in nodes) and at the Tool wrapping layer (nested interrupt scenarios)
+
+**Analogy:**
+
+- **simple tool** = “single-step operation” (read file)
+- **Graph Tool** = “pipeline” (read → chunk → score → filter → synthesize answer)
+
+## Key Concepts
+
+### compose.Workflow
+
+`compose.Workflow` is the core building block for workflows in Eino:
+
+```go
+wf := compose.NewWorkflow[Input, Output]()
+
+// Add nodes.
+wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
+wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
+wf.AddLambdaNode("score", scoreFunc).AddInput("chunk")
+wf.AddLambdaNode("answer", answerFunc).AddInput("score")
+
+// Connect to END.
+wf.End().AddInput("answer")
+```
+
+**Core concepts:**
+
+- **Node**: a processing unit in the workflow
+- **Edge**: data-flow connections between nodes
+- **START**: workflow entry
+- **END**: workflow exit
+
+### BatchNode
+
+`BatchNode` processes multiple tasks in parallel:
+
+```go
+scorer := batch.NewBatchNode(&batch.NodeConfig[Task, Result]{
+ Name: "ChunkScorer",
+ InnerTask: scoreOneChunk, // function to process a single task
+ MaxConcurrency: 5, // max concurrency
+})
+```
+
+**How it works:**
+
+1. takes a list of tasks as input
+2. runs tasks in parallel (limited by MaxConcurrency)
+3. collects and returns results
+
+### FieldMapping
+
+`FieldMapping` passes data across nodes:
+
+```go
+wf.AddLambdaNode("answer", answerFunc).
+ AddInputWithOptions("filter", // get data from the filter node
+ []*compose.FieldMapping{compose.ToField("TopK")},
+ compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, // get data from START
+ []*compose.FieldMapping{compose.MapFields("Question", "Question")},
+ compose.WithNoDirectDependency())
+```
+
+**Why FieldMapping?**
+
+- pass data between non-adjacent nodes
+- merge multiple data sources into one node
+- rename fields
+
+## Implementing a Graph Tool
+
+### 1. Define Input and Output Types
+
+```go
+type Input struct {
+ FilePath string `json:"file_path" jsonschema:"description=Absolute path to the document"`
+ Question string `json:"question" jsonschema:"description=The question to answer"`
+}
+
+type Output struct {
+ Answer string `json:"answer"`
+ Sources []string `json:"sources"`
+}
+```
+
+### 2. Build the Workflow
+
+```go
+func buildWorkflow(cm model.BaseChatModel) *compose.Workflow[Input, Output] {
+ wf := compose.NewWorkflow[Input, Output]()
+
+ // load: read file
+ wf.AddLambdaNode("load", compose.InvokableLambda(
+ func(ctx context.Context, in Input) ([]*schema.Document, error) {
+ data, err := os.ReadFile(in.FilePath)
+ if err != nil {
+ return nil, err
+ }
+ return []*schema.Document{{Content: string(data)}}, nil
+ },
+ )).AddInput(compose.START)
+
+ // chunk: split into chunks
+ wf.AddLambdaNode("chunk", compose.InvokableLambda(
+ func(ctx context.Context, docs []*schema.Document) ([]*schema.Document, error) {
+ var out []*schema.Document
+ for _, d := range docs {
+ out = append(out, splitIntoChunks(d.Content, 800)...)
+ }
+ return out, nil
+ },
+ )).AddInput("load")
+
+ // score: score in parallel
+ scorer := batch.NewBatchNode(&batch.NodeConfig[scoreTask, scoredChunk]{
+ Name: "ChunkScorer",
+ InnerTask: newScoreWorkflow(cm),
+ MaxConcurrency: 5,
+ })
+
+ wf.AddLambdaNode("score", compose.InvokableLambda(
+ func(ctx context.Context, in scoreIn) ([]scoredChunk, error) {
+ tasks := make([]scoreTask, len(in.Chunks))
+ for i, c := range in.Chunks {
+ tasks[i] = scoreTask{Text: c.Content, Question: in.Question}
+ }
+ return scorer.Invoke(ctx, tasks)
+ },
+ )).
+ AddInputWithOptions("chunk", []*compose.FieldMapping{compose.ToField("Chunks")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+ // filter: select top-k
+ wf.AddLambdaNode("filter", compose.InvokableLambda(
+ func(ctx context.Context, scored []scoredChunk) ([]scoredChunk, error) {
+ sort.Slice(scored, func(i, j int) bool {
+ return scored[i].Score > scored[j].Score
+ })
+ // keep top-3
+ if len(scored) > 3 {
+ scored = scored[:3]
+ }
+ return scored, nil
+ },
+ )).AddInput("score")
+
+ // answer: synthesize answer
+ wf.AddLambdaNode("answer", compose.InvokableLambda(
+ func(ctx context.Context, in synthIn) (Output, error) {
+ return synthesize(ctx, cm, in)
+ },
+ )).
+ AddInputWithOptions("filter", []*compose.FieldMapping{compose.ToField("TopK")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+ wf.End().AddInput("answer")
+
+ return wf
+}
+```
+
+### 3. Wrap as a Tool
+
+```go
+func BuildTool(ctx context.Context, cm model.BaseChatModel) (tool.BaseTool, error) {
+ wf := buildWorkflow(cm)
+ return graphtool.NewInvokableGraphTool[Input, Output](
+ wf,
+ "answer_from_document",
+ "Search a large document for relevant content and synthesize an answer.",
+ )
+}
+```
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [rag/rag.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/rag/rag.go)):
+
+```go
+// Build workflow.
+wf := compose.NewWorkflow[Input, Output]()
+
+// Add nodes.
+wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
+wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
+wf.AddLambdaNode("score", scoreFunc).
+ AddInputWithOptions("chunk", []*compose.FieldMapping{compose.ToField("Chunks")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+// Wrap as a Tool.
+return graphtool.NewInvokableGraphTool[Input, Output](wf, "answer_from_document", "...")
+```
+
+## Graph Tool Execution Flow
+
+```
+┌─────────────────────────────────────────┐
+│ input: file_path, question │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ load: read file │
+ │ out: []*Document │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ chunk: split │
+ │ out: []*Document │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ score: parallel │
+ │ (MaxConcurrency=5) │
+ │ out: []scoredChunk │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ filter: top-k │
+ │ out: []scoredChunk │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ answer: synthesize │
+ │ out: Output │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ return result │
+ │ {answer, sources} │
+ └──────────────────────┘
+```
+
+## Summary
+
+- **Graph Tool**: wrap complex workflows as a Tool for multi-step collaboration
+- **compose.Workflow**: core component for building workflows
+- **BatchNode**: parallel task processing
+- **FieldMapping**: pass data across nodes
+- **Interrupt/resume**: Graph Tools support checkpoints
+
+## Further Thoughts
+
+**Other Graph Tool applications:**
+
+- multi-document RAG: process multiple documents in parallel
+- multi-model collaboration: different models handle different tasks
+- complex decision trees: choose branches by conditions
+
+**Performance optimization:**
+
+- tune MaxConcurrency to control parallelism
+- use caching to avoid recomputation
+- stream outputs to improve user experience
diff --git a/content/en/docs/eino/quick_start/chapter_09_a2ui_protocol.md b/content/en/docs/eino/quick_start/chapter_09_a2ui_protocol.md
new file mode 100644
index 0000000000..ede6c9362b
--- /dev/null
+++ b/content/en/docs/eino/quick_start/chapter_09_a2ui_protocol.md
@@ -0,0 +1,344 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: "Chapter 9: A2UI Protocol (Streaming UI Components)"
+weight: 9
+---
+
+Goal of this chapter: implement the A2UI protocol and render Agent outputs as streaming UI components.
+
+## Important Note: The Scope of A2UI
+
+A2UI is not part of the Eino framework itself. It is an application-layer UI protocol/rendering approach. This chapter integrates A2UI into the Agent we built in previous chapters to provide an end-to-end, runnable example: model calls, tool calls, workflow orchestration, and finally presenting results with a more user-friendly UI.
+
+In real products, you can choose different UI forms depending on your needs, for example:
+
+- Web / App: custom components, tables, cards, charts, etc.
+- IM / office suite: message cards, interactive forms
+- CLI: plain text or TUI (terminal UI)
+
+Eino focuses on “composable execution and orchestration”. “How to present results to users” is an application-layer extension point.
+
+## Code Location
+
+- Entry code: [main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/main.go)
+- A2UI implementation: [a2ui/streamer.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/streamer.go)
+
+## Prerequisites
+
+Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
+
+## Run
+
+From `examples/quickstart/chatwitheino`:
+
+```bash
+go run .
+```
+
+Example output:
+
+```
+starting server on http://localhost:8080
+```
+
+## From Text to UI: Why A2UI
+
+In the first eight chapters, our Agent output only text, but modern AI applications often require richer interactions.
+
+**Limitations of pure text:**
+
+- cannot display structured data (tables, lists, cards, etc.)
+- cannot update in real time (progress bars, status changes, etc.)
+- cannot embed interactive elements (buttons, forms, links, etc.)
+- cannot support multimedia (images, video, audio, etc.)
+
+**What A2UI is for:**
+
+- **A2UI is a protocol from Agent to UI**: defines how Agent outputs map to UI components
+- **A2UI supports streaming rendering**: components can update in real time
+- **A2UI is declarative**: the Agent declares “what to display”, and the UI renders it
+
+**Analogy:**
+
+- **text output** = “terminal CLI” (text only)
+- **A2UI** = “web app UI” (can display arbitrary components)
+
+## Key Concepts
+
+### A2UI Components
+
+A2UI defines a series of UI component types:
+
+```go
+type ComponentType string
+
+const (
+ ComponentText ComponentType = "text" // text
+ ComponentMarkdown ComponentType = "markdown" // Markdown
+ ComponentCode ComponentType = "code" // code block
+ ComponentImage ComponentType = "image" // image
+ ComponentTable ComponentType = "table" // table
+ ComponentCard ComponentType = "card" // card
+ ComponentButton ComponentType = "button" // button
+ ComponentForm ComponentType = "form" // form
+ ComponentProgress ComponentType = "progress" // progress
+ ComponentDivider ComponentType = "divider" // divider
+)
+```
+
+### A2UI Message
+
+Each A2UI message contains:
+
+```go
+type Message struct {
+ ID string // message ID
+ Role string // user / assistant
+ Components []Component // UI component list
+ Timestamp time.Time // timestamp
+}
+```
+
+### A2UI Streaming Output
+
+A2UI supports streaming component updates:
+
+```go
+type StreamMessage struct {
+ Type string // add / update / delete
+ Index int // component index
+ Component Component // component payload
+}
+```
+
+**Streaming update types:**
+
+- `add`: add a new component
+- `update`: update an existing component
+- `delete`: delete a component
+
+## Implementing A2UI
+
+### 1. Create an A2UI Streamer
+
+```go
+streamer := a2ui.NewStreamer()
+```
+
+### 2. Add Components
+
+```go
+// Add a text component.
+streamer.AddText("Processing your request...")
+
+// Add a progress bar.
+streamer.AddProgress(0, 100, "Loading")
+
+// Update progress.
+streamer.UpdateProgress(0, 50, "Working")
+
+// Add a code block.
+streamer.AddCode("go", `fmt.Println("Hello, World!")`)
+
+// Add a table.
+streamer.AddTable([][]string{
+ {"Name", "Age", "City"},
+ {"Alice", "30", "New York"},
+ {"Bob", "25", "London"},
+})
+```
+
+### 3. Stream Output
+
+```go
+// Get the stream.
+stream := streamer.Stream()
+
+for {
+ msg, ok := stream.Next()
+ if !ok {
+ break
+ }
+ // Send to frontend.
+ sendToClient(msg)
+}
+```
+
+Key snippet (note: this is a simplified excerpt and not directly runnable; see [cmd/ch09/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch09/main.go)):
+
+```go
+// Create an A2UI Streamer.
+streamer := a2ui.NewStreamer()
+
+// Add components during Agent execution.
+streamer.AddText("Let me analyze this file for you...")
+
+// Call a Tool.
+streamer.AddProgress(0, 0, "Reading file")
+result, err := tool.Run(ctx, args)
+streamer.UpdateProgress(0, 100, "Done")
+
+// Show result.
+streamer.AddCode("json", result)
+
+// Stream output.
+stream := streamer.Stream()
+for {
+ msg, ok := stream.Next()
+ if !ok {
+ break
+ }
+ wsConn.WriteJSON(msg)
+}
+```
+
+## Integrating A2UI with the Agent
+
+### Use A2UI in the Agent
+
+```go
+func buildAgent(ctx context.Context) (adk.Agent, error) {
+ return deep.New(ctx, &deep.Config{
+ Name: "A2UIAgent",
+ Description: "Agent with A2UI streaming output",
+ ChatModel: cm,
+ Backend: backend,
+ StreamingShell: backend,
+ })
+}
+```
+
+### Use A2UI in the Runner
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+})
+
+// Execute Agent.
+events := runner.Run(ctx, history)
+
+// Convert events into A2UI components.
+streamer := a2ui.NewStreamer()
+for {
+ event, ok := events.Next()
+ if !ok {
+ break
+ }
+ if event.Output != nil && event.Output.MessageOutput != nil {
+ // Add a text component.
+ streamer.AddText(event.Output.MessageOutput.Message.Content)
+ }
+}
+```
+
+## A2UI Streaming Rendering Flow
+
+```
+┌─────────────────────────────────────────┐
+│ user: analyze this file │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ agent starts │
+ │ A2UI: AddText │
+ │ "analyzing..." │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ call Tool │
+ │ A2UI: AddProgress │
+ │ progress: 0% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Tool running │
+ │ A2UI: UpdateProgress │
+ │ progress: 50% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Tool done │
+ │ A2UI: UpdateProgress │
+ │ progress: 100% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ show result │
+ │ A2UI: AddCode │
+ │ code block │
+ └──────────────────────┘
+```
+
+## Frontend Integration
+
+### WebSocket Connection
+
+```javascript
+const ws = new WebSocket('ws://localhost:8080/ws');
+
+ws.onmessage = (event) => {
+ const msg = JSON.parse(event.data);
+ renderComponent(msg);
+};
+
+function renderComponent(msg) {
+ const { type, index, component } = msg;
+
+ switch (component.type) {
+ case 'text':
+ renderText(component.content);
+ break;
+ case 'code':
+ renderCode(component.language, component.content);
+ break;
+ case 'progress':
+ renderProgress(component.value, component.max, component.label);
+ break;
+ // ...
+ }
+}
+```
+
+## Summary
+
+- **A2UI**: Agent-to-UI protocol that maps Agent output to UI components
+- **Component types**: text, Markdown, code, images, tables, cards, buttons, forms, progress bars, etc.
+- **Streaming output**: add/update/delete components in real time
+- **Declarative**: the Agent declares “what to show”; the UI renders it
+- **Frontend integration**: real-time communication via WebSocket
+
+## Series Wrap-Up: The End-to-End Quickstart Agent
+
+By this chapter, we have connected Eino’s core capabilities into a runnable Agent. You can view it as an extensible “end-to-end agent application skeleton”:
+
+- runtime: Runner-driven execution, streaming output and event model
+- tools: filesystem/shell tools integrated, tool errors handled safely
+- middleware: pluggable handlers for error handling, retries, approvals, etc.
+- observability: callbacks/trace to connect key paths for debugging and production monitoring
+- human-in-the-loop: interrupt/resume + checkpoint for approvals, parameter completion, branch selection, etc.
+- deterministic orchestration: compose (graph/chain/workflow) organizes complex business processes as maintainable, reusable execution graphs
+- delivery: UI integration like A2UI is application-layer, letting you present agent capabilities in the right product form
+
+You can gradually replace/extend any part—model, tools, storage, workflows, UI protocol—without rebuilding from scratch.
+
+## Further Thoughts
+
+**Other component types:**
+
+- chart components (line, bar, pie, etc.)
+- map components
+- timeline components
+- tree components
+- tab components
+
+**Advanced features:**
+
+- component interactions (click, drag, input)
+- conditional rendering
+- component animations
+- responsive layouts
diff --git a/content/en/docs/eino/quick_start/simple_llm_application.md b/content/en/docs/eino/quick_start/simple_llm_application.md
deleted file mode 100644
index 219c8c1a97..0000000000
--- a/content/en/docs/eino/quick_start/simple_llm_application.md
+++ /dev/null
@@ -1,207 +0,0 @@
----
-Description: ""
-date: "2026-01-20"
-lastmod: ""
-tags: []
-title: Building a Simple LLM Application
-weight: 1
----
-
-This guide will help you quickly get started using Eino framework's ChatModel to build a simple LLM application. We will demonstrate how to use ChatModel through implementing a "Programmer Encouragement Assistant" example.
-
-> 💡
-> Code snippets from the examples in this article can be found at: [eino-examples/quickstart/chat](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chat)
-
-## **ChatModel Introduction**
-
-ChatModel is Eino framework's abstraction for conversational large models, providing a unified interface to interact with different large model services (such as OpenAI, Ollama, etc.).
-
-> For more detailed component introduction, see: [Eino: ChatModel Guide](/docs/eino/core_modules/components/chat_model_guide)
-
-## **Message Structure and Usage**
-
-In Eino, conversations are represented through `schema.Message`, which is Eino's abstract definition of a conversation message. Each Message contains the following important fields:
-
-- `Role`: The role of the message, which can be:
- - `system`: System instruction, used to set the model's behavior and role
- - `user`: User input
- - `assistant`: Model's reply
- - `tool`: Tool call result
-- `Content`: The specific content of the message
-
-## **Implementing the Programmer Encouragement Assistant**
-
-Let's learn how to use ChatModel by implementing a Programmer Encouragement Assistant. This assistant can not only provide technical advice but also offer psychological support when programmers feel discouraged.
-
-### Creating Conversation Templates and Generating Messages
-
-Eino provides powerful templating functionality for building messages to input to the large model:
-
-1. Template rendering, supporting three template formats:
-
- - FString: Python-style simple string formatting (e.g., "Hello, {name}!")
- - Jinja2: Jinja2-style templates supporting rich expressions (e.g., "Hello, {{name}}!")
- - GoTemplate: Go's built-in text/template format (e.g., "Hello, {{.name}}!")
-2. Message placeholders: Support inserting a group of messages (such as conversation history)
-
-```go
-// optional=false means required message list, will error if corresponding variable not found in template input
-schema.MessagesPlaceholder("chat_history", false)
-```
-
-> For more detailed component introduction, see: [Eino: ChatTemplate Guide](/docs/eino/core_modules/components/chat_template_guide)
-
-Below is the complete code for creating and using conversation templates with FString format + message placeholders:
-
-```go
-// eino-examples/quickstart/chat/template.go
-
-import (
- "context"
-
- "github.com/cloudwego/eino/components/prompt"
- "github.com/cloudwego/eino/schema"
-)
-
-// Create template using FString format
-template := prompt.FromMessages(schema.FString,
- // System message template
- schema.SystemMessage("You are a {role}. You need to respond in a {style} tone. Your goal is to help programmers maintain a positive and optimistic mindset, providing technical advice while also caring about their mental health."),
-
- // Insert needed conversation history (leave empty for new conversations)
- schema.MessagesPlaceholder("chat_history", true),
-
- // User message template
- schema.UserMessage("Question: {question}"),
-)
-
-// Use template to generate messages
-messages, err := template.Format(context.Background(), map[string]any{
- "role": "Programmer Encouragement Assistant",
- "style": "positive, warm, and professional",
- "question": "My code keeps throwing errors, I feel so frustrated, what should I do?",
- // Conversation history (simulating two rounds of conversation history in this example)
- "chat_history": []*schema.Message{
- schema.UserMessage("Hello"),
- schema.AssistantMessage("Hey! I'm your Programmer Encouragement Assistant! Remember, every excellent programmer grows through debugging. How can I help you?", nil),
- schema.UserMessage("I feel like the code I write is terrible"),
- schema.AssistantMessage("Every programmer goes through this stage! What's important is that you're constantly learning and improving. Let's look at the code together, I believe through refactoring and optimization, it will get better. Remember, Rome wasn't built in a day, code quality improves through continuous improvement.", nil),
- },
-})
-```
-
-### Creating a ChatModel
-
-ChatModel is one of the most core components in the Eino framework, providing a unified interface for interacting with various large language models. Eino currently supports the following large language model implementations:
-
-- OpenAI: Supports GPT-3.5/GPT-4 and other models (also supports Azure-provided OpenAI services)
-- Ollama: Supports locally deployed open-source models
-- Ark: Model services on Volcano Engine (e.g., ByteDance's Doubao model)
-- More models are being added
-
-> For supported models, see: [Eino: Ecosystem Integration](/docs/eino/ecosystem_integration)
-
-Below we demonstrate how to create and use ChatModel using OpenAI and Ollama as examples:
-
-#### **OpenAI (choose either this or Ollama below)**
-
-```go
-// eino-examples/quickstart/chat/openai.go
-
-import (
- "os"
-
- "github.com/cloudwego/eino-ext/components/model/openai"
-)
-
-chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
- Model: "gpt-4o", // Model version to use
- APIKey: os.Getenv("OPENAI_API_KEY"), // OpenAI API key
-})
-```
-
-> For detailed information about OpenAI ChatModel, see: [ChatModel - OpenAI](https://github.com/cloudwego/eino-ext/blob/main/components/model/openai/README.md)
-
-#### **Ollama (choose either this or OpenAI above)**
-
-Ollama supports running open-source models locally, suitable for scenarios with data privacy requirements or offline usage needs.
-
-```go
-// eino-examples/quickstart/chat/ollama.go
-
-import (
- "github.com/cloudwego/eino-ext/components/model/ollama"
-)
-
-
-chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
- BaseURL: "http://localhost:11434", // Ollama service address
- Model: "llama2", // Model name
-})
-```
-
-> For detailed information about Ollama ChatModel, see: [ChatModel - Ollama](https://github.com/cloudwego/eino-ext/blob/main/components/model/ollama/README.md)
-
-Eino provides a unified ChatModel abstraction for large models and offers ready-to-use implementations of various LLMs. Therefore, business code can focus on writing business logic without worrying about model implementation details. When model implementations are updated, they won't affect core business logic, meaning developers can easily switch between different models without modifying large amounts of code.
-
-### Running the ChatModel
-
-After obtaining the ChatModel input messages and the initialized ChatModel instance through the previous two steps, you can start trying to run the ChatModel. Eino ChatModel provides two running modes: output complete message (generate) and output message stream (stream):
-
-```go
-// eino-examples/quickstart/chat/generate.go
-
-/*** create messages
-* messages, err := xxx
-*/
-
-/*** create chat model
-* chatModel, err := xxx
-*/
-
-result, err := chatModel.Generate(ctx, messages)
-streamResult, err := chatModel.Stream(ctx, messages)
-```
-
-In practical applications, there are many scenarios that need streaming responses. The main scenario is "improving user experience": the stream running mode allows ChatModel to provide typewriter-like output, giving users model responses earlier.
-
-Here's how Eino handles streaming output:
-
-```go
-// eino-examples/quickstart/chat/stream.go
-
-import (
- "io"
- "log"
-
- "github.com/cloudwego/eino/schema"
-)
-
-func reportStream(sr *schema.StreamReader[*schema.Message]) {
- defer sr.Close()
-
- i := 0
- for {
- message, err := sr.Recv()
- if err == io.EOF { // Streaming output ended
- return
- }
- if err != nil {
- log.Fatalf("recv failed: %v", err)
- }
- log.Printf("message[%d]: %+v\n", i, message)
- i++
- }
-}
-```
-
-For the complete implementation, see: [eino-examples/quickstart/chat/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chat/main.go)
-
-## **Summary**
-
-This example demonstrated how to build an LLM application using the Eino framework through a Programmer Encouragement Assistant case. From creating a ChatModel to using message templates to actual conversation implementation, you should now have a basic understanding of the Eino framework. Whether you choose OpenAI, Ollama, or other model implementations, Eino provides a unified and simple way to use them. We hope this example helps you quickly start building your own LLM applications.
-
-## **Related Reading**
-
-- Quick Start
- - [Agent - Give LLMs Hands](/docs/eino/quick_start/agent_llm_with_tools)
diff --git a/content/zh/docs/eino/FAQ.md b/content/zh/docs/eino/FAQ.md
index d062f4d4b9..4dd9e856d0 100644
--- a/content/zh/docs/eino/FAQ.md
+++ b/content/zh/docs/eino/FAQ.md
@@ -142,7 +142,7 @@ eino-ext 支持的多模态输入输出场景,可以查阅 [https://www.cloudw
eino-ext 部分 module 报错 undefined: schema.NewParamsOneOfByOpenAPIV3 等问题,升级报错的 eino-ext module 到最新版本即可。
-如果 schema 改造比较复杂,可以使用现有的 OpenAPI 3.0 → JSONSchema 转换工具方法辅助转换。
+如果 schema 改造比较复杂,可以使用 JSONSchema 转换工具方法辅助转换。
# Q: Eino-ext 提供的 ChatModel 有哪些模型是支持 Response API 形式调用嘛?
diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_\346\234\254\345\234\260\346\226\207\344\273\266\347\263\273\347\273\237.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_\346\234\254\345\234\260\346\226\207\344\273\266\347\263\273\347\273\237.md"
index 356d445ddd..4f1b07ab90 100644
--- "a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_\346\234\254\345\234\260\346\226\207\344\273\266\347\263\273\347\273\237.md"
+++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_\346\234\254\345\234\260\346\226\207\344\273\266\347\263\273\347\273\237.md"
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-10"
+date: "2026-03-12"
lastmod: ""
tags: []
title: 本地文件系统
@@ -218,6 +218,10 @@ absPath, _ := filepath.Abs("./relative/path")
### 常见问题
+**Q: 为什么运行 grep 命令报错 ripgrep (rg) is not installed or not in PATH. Please install it: ****[https://github.com/BurntSushi/ripgrep#installation](https://github.com/BurntSushi/ripgrep#installation)**
+
+local 的 Grep 命令默认依赖** ripgrep **指令,如系统没有预装 ripgrep 则需要通过文档安装 ripgrep
+
**Q: GrepRaw 支持正则吗?**
支持正则匹配,GrepRaw 底层使用的是 ripgrep 命令做的 Grep 操作
diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/_index.md b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/_index.md
index e108aabe97..498bbcfd72 100644
--- a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/_index.md
+++ b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/_index.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-02"
+date: "2026-03-12"
lastmod: ""
tags: []
title: FileSystem
diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_Skill.md b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_Skill.md
index beaa5fa085..b9f9e6ebe7 100644
--- a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_Skill.md
+++ b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_Skill.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-10"
+date: "2026-03-12"
lastmod: ""
tags: []
title: Skill
@@ -283,7 +283,7 @@ type Config struct {
# 快速开始
-以从本地加载 pdf skill 为例, 完整代码见 [https://github.com/cloudwego/eino-examples/tree/alpha/08/adk/middlewares/skill](https://github.com/cloudwego/eino-examples/tree/alpha/08/adk/middlewares/skill)。
+以从本地加载 pdf skill 为例, 完整代码见 [https://github.com/cloudwego/eino-examples/tree/main/adk/middlewares/skill](https://github.com/cloudwego/eino-examples/tree/main/adk/middlewares/skill)。
- 在工作目录中创建 skills 目录:
@@ -305,20 +305,24 @@ import (
"github.com/cloudwego/eino-ext/adk/backend/local"
)
+ctx := context.Background()
be, err := local.NewBackend(ctx, &local.Config{})
if err != nil {
log.Fatal(err)
}
-wd, _ := os.Getwd()
-workDir := filepath.Join(wd, "adk", "middlewares", "skill", "workdir")
skillBackend, err := skill.NewBackendFromFilesystem(ctx, &skill.BackendFromFilesystemConfig{
Backend: be,
BaseDir: skillsDir,
})
+if err != nil {
+ log.Fatalf("Failed to create skill backend: %v", err)
+}
-skillMiddleware, err := NewMiddleware(ctx, &Config{Backend: backend})
+sm, err := skill.NewMiddleware(ctx, &skill.Config{
+ Backend: skillBackend,
+})
```
- 基于 backend 创建本地 Filesystem Middleware,供 agent 读取 skill 其他文件以及执行脚本:
@@ -329,8 +333,8 @@ import (
)
fsm, err := filesystem.New(ctx, &filesystem.MiddlewareConfig{
- Backend: be,
- WithoutLargeToolResultOffloading: true,
+ Backend: be,
+ StreamingShell: be,
})
```
@@ -342,7 +346,7 @@ agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Description: "An agent that can analyze logs",
Instruction: "You are a helpful assistant.",
Model: cm,
- Handlers: []adk.ChatModelAgentMiddleware{fsm, skillMiddleware},
+ Handlers: []adk.ChatModelAgentMiddleware{fsm, sm},
})
```
@@ -502,4 +506,4 @@ Important:
> 💡
-> Skill Middleware 仅提供了如上图所示的加载 SKILL.md 能力,如果 Skill 本身需要读取文件、执行脚本等,需要用户另外为 agent 配置相关能力。
+> Skill Middleware 仅提供了如上图所示的加载 SKILL.md 能力,如果 Skill 需要 agent 具备读取文件、执行脚本等能力,需要用户另外为 agent 配置。
diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_ToolReduction.md b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_ToolReduction.md
index 309c9ab004..fae42a03c9 100644
--- a/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_ToolReduction.md
+++ b/content/zh/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_ToolReduction.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-09"
+date: "2026-03-12"
lastmod: ""
tags: []
title: Reduction
diff --git a/content/zh/docs/eino/core_modules/eino_adk/adk_agent_callback.md b/content/zh/docs/eino/core_modules/eino_adk/adk_agent_callback.md
index 1088b50d6f..90b1d677e6 100644
--- a/content/zh/docs/eino/core_modules/eino_adk/adk_agent_callback.md
+++ b/content/zh/docs/eino/core_modules/eino_adk/adk_agent_callback.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-03-10"
+date: "2026-03-12"
lastmod: ""
tags: []
title: Agent Callback
diff --git a/content/zh/docs/eino/ecosystem_integration/_index.md b/content/zh/docs/eino/ecosystem_integration/_index.md
index ca3673709a..1dcb09ae79 100644
--- a/content/zh/docs/eino/ecosystem_integration/_index.md
+++ b/content/zh/docs/eino/ecosystem_integration/_index.md
@@ -11,9 +11,9 @@ weight: 6
### ChatModel
-- openai: [ChatModel - OpenAI](https://github.com/cloudwego/eino-ext/blob/main/components/model/openai/README_zh.md)
-- ark: [ChatModel - ARK](https://github.com/cloudwego/eino-ext/blob/main/components/model/ark/README_zh.md)
-- ollama: [ChatModel - Ollama](https://github.com/cloudwego/eino-ext/blob/main/components/model/ollama/README_zh.md)
+- openai: [OpenAI](/zh/docs/eino/ecosystem_integration/chat_model/agentic_model_openai)
+- ark: [ARK](/zh/docs/eino/ecosystem_integration/chat_model/agentic_model_ark)
+- 更多组件请见:[ChatModel 组件列表](/zh/docs/eino/ecosystem_integration/chat_model)
### Document
diff --git a/content/zh/docs/eino/quick_start/_index.md b/content/zh/docs/eino/quick_start/_index.md
index 78ae777d85..b2f99f8978 100644
--- a/content/zh/docs/eino/quick_start/_index.md
+++ b/content/zh/docs/eino/quick_start/_index.md
@@ -19,7 +19,7 @@ Eino 提供了多种面向 AI 应用开发场景的组件抽象,同时也提
AI 的应用中,最基础的场景就是 prompt + chat model 的场景,这也是互联网上各类 AI 应用平台提供的最重要的功能。你可以定义 `System Prompt` 来约束大模型的回答逻辑,比如 “你在扮演一个 XXX 角色” 等等。这个示例中,你可以用 Eino 的 `PromptTemplate` 组件 和 `ChatModel` 组件来构建一个角色扮演应用。
-- [实现一个最简 LLM 应用-ChatModel](/zh/docs/eino/quick_start/simple_llm_application)
+- [第一章:ChatModel 与 Message(Console)](/zh/docs/eino/quick_start/chapter_01_chatmodel_and_message)
### 示例:创建一个 Agent
@@ -31,7 +31,7 @@ AI 的应用中,最基础的场景就是 prompt + chat model 的场景,这
在这个示例中,我们将使用 react agent 来构建一个可以和现实世界交互的智能体。
-- [Agent-让大模型拥有双手](/zh/docs/eino/quick_start/agent_llm_with_tools)
+- [第四章:Tool 与文件系统访问](/zh/docs/eino/quick_start/chapter_04_tool_and_filesystem)
## 下一步探索
diff --git a/content/zh/docs/eino/quick_start/agent_llm_with_tools.md b/content/zh/docs/eino/quick_start/agent_llm_with_tools.md
deleted file mode 100644
index 2694ed87a9..0000000000
--- a/content/zh/docs/eino/quick_start/agent_llm_with_tools.md
+++ /dev/null
@@ -1,300 +0,0 @@
----
-Description: ""
-date: "2026-03-03"
-lastmod: ""
-tags: []
-title: Agent-让大模型拥有双手
-weight: 2
----
-
-## **Agent 是什么**
-
-Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中主要的形态。
-
-> 💡
-> 本文中示例的代码片段详见:[eino-examples/quickstart/todoagent](https://github.com/cloudwego/eino-examples/blob/master/quickstart/todoagent/main.go)
-
-## **Agent 的核心组成**
-
-在 Eino 中,要实现 Agent 主要需要两个核心部分:ChatModel 和 Tool。
-
-### **ChatModel**
-
-ChatModel 是 Agent 的大脑,它通过强大的语言理解能力来处理用户的自然语言输入。当用户提出请求时,ChatModel 会深入理解用户的意图,分析任务需求,并决定是否需要调用特定的工具来完成任务。在需要使用工具时,它能够准确地选择合适的工具并生成正确的参数。不仅如此,ChatModel 还能将工具执行的结果转化为用户易于理解的自然语言回应,实现流畅的人机对话。
-
-> 更详细的 ChatModel 的信息,可以参考: [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide)
-
-### **Tool**
-
-Tool 是 Agent 的执行器,提供了具体的功能实现。每个 Tool 都有明确的功能定义和参数规范,使 ChatModel 能够准确地调用它们。Tool 可以实现各种功能,从简单的数据操作到复杂的外部服务调用都可以封装成 Tool。
-
-> 更详细关于 Tool 和 ToolsNode 的信息,可参考: [Eino: ToolsNode 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide)
-
-## **Tool 的实现方式**
-
-在 Eino 中,我们提供了多种方式来实现 Tool。下面通过一个待办事项(Todo)管理系统的例子来说明。
-
-### **方式一:使用 NewTool 构建**
-
-这种方式适合简单的工具实现,通过定义工具信息和处理函数来创建 Tool:
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/components/tool/utils"
- "github.com/cloudwego/eino/schema"
-)
-
-// 处理函数
-func AddTodoFunc(_ context.Context, params *TodoAddParams) (string, error) {
- // Mock处理逻辑
- return `{"msg": "add todo success"}`, nil
-}
-
-func getAddTodoTool() tool.InvokableTool {
- // 工具信息
- info := &schema.ToolInfo{
- Name: "add_todo",
- Desc: "Add a todo item",
- ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
- "content": {
- Desc: "The content of the todo item",
- Type: schema.String,
- Required: true,
- },
- "started_at": {
- Desc: "The started time of the todo item, in unix timestamp",
- Type: schema.Integer,
- },
- "deadline": {
- Desc: "The deadline of the todo item, in unix timestamp",
- Type: schema.Integer,
- },
- }),
- }
-
- // 使用NewTool创建工具
- return utils.NewTool(info, AddTodoFunc)
-}
-```
-
-这种方式虽然直观,但存在一个明显的缺点:需要在 ToolInfo 中手动定义参数信息(ParamsOneOf),和实际的参数结构(TodoAddParams)是分开定义的。这样不仅造成了代码的冗余,而且在参数发生变化时需要同时修改两处地方,容易导致不一致,维护起来也比较麻烦。
-
-### **方式二:使用 InferTool 构建**
-
-这种方式更加简洁,通过结构体的 tag 来定义参数信息,就能实现参数结构体和描述信息同源,无需维护两份信息:
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool/utils"
-)
-
-// 参数结构体
-type TodoUpdateParams struct {
- ID string `json:"id" jsonschema:"description=id of the todo"`
- Content *string `json:"content,omitempty" jsonschema:"description=content of the todo"`
- StartedAt *int64 `json:"started_at,omitempty" jsonschema:"description=start time in unix timestamp"`
- Deadline *int64 `json:"deadline,omitempty" jsonschema:"description=deadline of the todo in unix timestamp"`
- Done *bool `json:"done,omitempty" jsonschema:"description=done status"`
-}
-
-// 处理函数
-func UpdateTodoFunc(_ context.Context, params *TodoUpdateParams) (string, error) {
- // Mock处理逻辑
- return `{"msg": "update todo success"}`, nil
-}
-
-// 使用 InferTool 创建工具
-updateTool, err := utils.InferTool(
- "update_todo", // tool name
- "Update a todo item, eg: content,deadline...", // tool description
- UpdateTodoFunc)
-```
-
-### **方式三:实现 Tool 接口**
-
-对于需要更多自定义逻辑的场景,可以通过实现 Tool 接口来创建:
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/schema"
-)
-
-type ListTodoTool struct {}
-
-func (lt *ListTodoTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
- return &schema.ToolInfo{
- Name: "list_todo",
- Desc: "List all todo items",
- ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
- "finished": {
- Desc: "filter todo items if finished",
- Type: schema.Boolean,
- Required: false,
- },
- }),
- }, nil
-}
-
-func (lt *ListTodoTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
- // Mock调用逻辑
- return `{"todos": [{"id": "1", "content": "在2024年12月10日之前完成Eino项目演示文稿的准备工作", "started_at": 1717401600, "deadline": 1717488000, "done": false}]}`, nil
-}
-```
-
-### **方式四:使用****官方封装的工具**
-
-除了自己实现工具,我们还提供了许多开箱即用的工具。这些工具经过充分测试和优化,可以直接集成到你的 Agent 中。以 duckduckgo Search 工具为例:
-
-```go
-import (
- "github.com/cloudwego/eino-ext/components/tool/duckduckgo"
-)
-
-
-// 创建 duckduckgo Search 工具
-searchTool, err := duckduckgo.NewTool(ctx, &duckduckgo.Config{})
-```
-
-使用 eino-ext 提供的工具不仅能避免重复开发的工作量,还能确保工具的稳定性和可靠性。这些工具都经过充分测试和持续维护,可以直接集成到项目中使用。
-
-## **用 Chain 构建 Agent**
-
-在构建 Agent 时,ToolsNode 是一个核心组件,它负责管理和执行工具调用。ToolsNode 可以集成多个工具,并提供统一的调用接口。它支持同步调用(Invoke)和流式调用(Stream)两种方式,能够灵活地处理不同类型的工具执行需求。
-
-要创建一个 ToolsNode,你需要提供一个工具列表配置:
-
-```go
-import (
- "context"
-
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/compose"
-)
-
-conf := &compose.ToolsNodeConfig{
- Tools: []tool.BaseTool{tool1, tool2}, // 工具可以是 InvokableTool 或 StreamableTool
-}
-toolsNode, err := compose.NewToolNode(context.Background(), conf)
-```
-
-下面是一个完整的 Agent 示例,它使用 OpenAI 的 ChatModel 并结合了上述的 Todo 工具:
-
-```go
-import (
- "context"
- "fmt"
- "log"
- "os"
-
- "github.com/cloudwego/eino-ext/components/model/openai"
- "github.com/cloudwego/eino/components/tool"
- "github.com/cloudwego/eino/compose"
- "github.com/cloudwego/eino/schema"
-)
-
-func main() {
- // 初始化 tools
- todoTools := []tool.BaseTool{
- getAddTodoTool(), // NewTool 构建
- updateTool, // InferTool 构建
- &ListTodoTool{}, // 实现Tool接口
- searchTool, // 官方封装的工具
- }
-
- // 创建并配置 ChatModel
- chatModel, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{
- Model: "gpt-4",
- APIKey: os.Getenv("OPENAI_API_KEY"),
- })
- if err != nil {
- log.Fatal(err)
- }
- // 获取工具信息并绑定到 ChatModel
- toolInfos := make([]*schema.ToolInfo, 0, len(todoTools))
- for _, tool := range todoTools {
- info, err := tool.Info(ctx)
- if err != nil {
- log.Fatal(err)
- }
- toolInfos = append(toolInfos, info)
- }
- err = chatModel.BindTools(toolInfos)
- if err != nil {
- log.Fatal(err)
- }
-
-
- // 创建 tools 节点
- todoToolsNode, err := compose.NewToolNode(context.Background(), &compose.ToolsNodeConfig{
- Tools: todoTools,
- })
- if err != nil {
- log.Fatal(err)
- }
-
- // 构建完整的处理链
- chain := compose.NewChain[[]*schema.Message, []*schema.Message]()
- chain.
- AppendChatModel(chatModel, compose.WithNodeName("chat_model")).
- AppendToolsNode(todoToolsNode, compose.WithNodeName("tools"))
-
- // 编译并运行 chain
- agent, err := chain.Compile(ctx)
- if err != nil {
- log.Fatal(err)
- }
-
- // 运行示例
- resp, err := agent.Invoke(ctx, []*schema.Message{
- {
- Role: schema.User,
- Content: "添加一个学习 Eino 的 TODO,同时搜索一下 cloudwego/eino 的仓库地址",
- },
- })
- if err != nil {
- log.Fatal(err)
- }
-
- // 输出结果
- for _, msg := range resp {
- fmt.Println(msg.Content)
- }
-}
-```
-
-这个示例有一个假设,也就是 ChatModel 一定会做出 tool 调用的决策。
-
-## **使用其他方式构建 Agent**
-
-除了上述使用 Chain/Graph 构建的 agent 之外,Eino 还提供了常用的 Agent 模式的封装。
-
-### **ReAct Agent**
-
-ReAct(Reasoning + Acting)Agent 结合了推理和行动能力,通过思考-行动-观察的循环来解决复杂问题。它能够在执行任务时进行深入的推理,并根据观察结果调整策略,特别适合需要多步推理的复杂场景。
-
-> 更详细的 react agent 可以参考: [Eino: React Agent 使用手册](/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual)
-
-### **Multi Agent**
-
-Multi Agent 系统由多个协同工作的 Agent 组成,每个 Agent 都有其特定的职责和专长。通过 Agent 间的交互与协作,可以处理更复杂的任务,实现分工协作。这种方式特别适合需要多个专业领域知识结合的场景。
-
-> 更详细的 multi agent 可以参考: [Eino Tutorial: Host Multi-Agent ](/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting)
-
-## **总结**
-
-介绍了使用 Eino 框架构建 Agent 的基本方法。通过 Chain、Tool Calling 和 ReAct 等不同方式,我们可以根据实际需求灵活地构建 AI Agent。
-
-Agent 是 AI 技术发展的重要方向。它不仅能够理解用户意图,还能主动采取行动,通过调用各种工具来完成复杂任务。随着大语言模型能力的不断提升,Agent 将在未来扮演越来越重要的角色,成为连接 AI 与现实世界的重要桥梁。我们期待 Eino 能为用户带来更强大、易用的 agent 构建方案,推动更多基于 Agent 的应用创新。
-
-## **关联阅读**
-
-- 快速开始
- - [实现一个最简 LLM 应用-ChatModel](/zh/docs/eino/quick_start/simple_llm_application)
diff --git a/content/zh/docs/eino/quick_start/chapter_01_chatmodel_and_message.md b/content/zh/docs/eino/quick_start/chapter_01_chatmodel_and_message.md
new file mode 100644
index 0000000000..e961e52b1b
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_01_chatmodel_and_message.md
@@ -0,0 +1,222 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第一章:ChatModel 与 Message(Console)
+weight: 1
+---
+
+## Eino 框架简介
+
+**Eino 是什么?**
+
+Eino 是一个 Go 语言实现的 AI 应用开发框架(Agent Development Kit),旨在帮助开发者快速构建可扩展、可维护的 AI 应用。
+
+**Eino 解决什么问题?**
+
+1. **模型抽象**:统一不同 LLM 提供商的接口(OpenAI、Ark、Claude 等),切换模型无需修改业务代码
+2. **能力组合**:通过 Component 接口实现可替换、可组合的能力单元(对话、工具、检索等)
+3. **编排框架**:提供 Agent、Graph、Chain 等编排抽象,支持复杂的多步骤 AI 工作流
+4. **运行时支持**:内置流式输出、中断与恢复、状态管理、Callback 可观测性等能力
+
+**Eino 的主要仓库:**
+
+- **eino**(本仓库):核心库,定义接口、编排抽象和 ADK
+- **eino-ext**:扩展库,提供各类 Component 的具体实现(OpenAI、Ark、Milvus 等)
+- **eino-examples**:示例代码库,包含本 quickstart 系列
+
+---
+
+## ChatWithEino:与 Eino 文档对话的智能助手
+
+**ChatWithEino 是什么?**
+
+ChatWithEino 是一个基于 Eino 框架构建的智能助手,能够帮助开发者学习 Eino 框架并编写 Eino 代码。它通过访问 Eino 仓库的源码、注释和示例,为用户提供最准确、最及时的技术支持。
+
+**核心能力:**
+
+- **对话交互**:理解用户关于 Eino 的问题,提供清晰的解答
+- **代码访问**:直接读取 Eino 源码、注释和示例,基于真实实现回答问题
+- **持久化会话**:支持多轮对话,记住上下文,可跨进程恢复会话
+- **工具调用**:能够执行文件读取、代码搜索等操作
+
+**技术架构:**
+
+- **ChatModel**:与大语言模型通信(OpenAI、Ark、Claude 等)
+- **Tool**:文件系统访问、代码搜索等能力扩展
+- **Memory**:对话历史持久化存储
+- **Agent**:统一的执行框架,协调各组件协同工作
+
+## Quickstart 文档系列:从零构建 ChatWithEino
+
+本系列文档通过循序渐进的方式,带你从最基础的 ChatModel 调用开始,逐步构建一个功能完整的 ChatWithEino Agent。
+
+**学习路径:**
+
+
+| 章节 | 主题 | 核心内容 | 能力提升 |
+| 第一章 | ChatModel 与 Message | 理解 Component 抽象,实现单次对话 | 基础对话能力 |
+| 第二章 | Agent 与 Runner | 引入执行抽象,实现多轮对话 | 会话管理能力 |
+| 第三章 | Memory 与 Session | 持久化对话历史,支持会话恢复 | 持久化能力 |
+| 第四章 | Tool 与文件系统 | 添加文件访问能力,读取源码 | 工具调用能力 |
+| 第五章 | Middleware | 中间件机制,统一处理横切关注点 | 扩展性增强 |
+| 第六章 | Callback | 回调机制,监控 Agent 执行过程 | 可观测性 |
+| 第七章 | Interrupt 与 Resume | 中断与恢复,支持长时间任务 | 可靠性增强 |
+| 第八章 | Graph 与 Tool | 使用 Graph 编排复杂工作流 | 复杂编排能力 |
+| 第九章 | A2UI | Agent 到 UI 的集成方案 | 生产级应用 |
+
+
+**为什么这样设计?**
+
+每一章都在前一章的基础上增加一个核心能力,让你:
+
+1. **理解每个组件的作用**:不是一次性展示所有功能,而是逐步引入
+2. **看到架构演进过程**:从简单到复杂,理解为什么需要每个抽象
+3. **掌握实际开发技能**:每章都有可运行的代码,可以动手实践
+
+---
+
+本章目标:理解 Eino 的 Component 抽象,用最小代码调用一次 ChatModel(支持流式输出),并掌握 `schema.Message` 的基本用法。
+
+## 代码位置
+
+- 入口代码:[cmd/ch01/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch01/main.go)
+
+## 为什么需要 Component 接口
+
+Eino 定义了一组 Component 接口(`ChatModel`、`Tool`、`Retriever`、`Loader` 等),每个接口描述一类可替换的能力:
+
+```go
+type BaseChatModel interface {
+ Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error)
+ Stream(ctx context.Context, input []*schema.Message, opts ...Option) (
+ *schema.StreamReader[*schema.Message], error)
+}
+```
+
+**接口带来的好处:**
+
+1. **实现可替换**:`eino-ext` 提供了 OpenAI、Ark、Claude、Ollama 等多种实现,业务代码只依赖接口,切换模型只需改构造逻辑。
+2. **编排可组合**:Agent、Graph、Chain 等编排层只依赖 Component 接口,不关心具体实现。你可以把 OpenAI 换成 Ark,编排代码无需改动。
+3. **测试可 Mock**:接口天然支持 mock,单元测试不需要真实调用模型。
+
+本章只涉及 `ChatModel`,后续章节会逐步引入 `Tool`、`Retriever` 等 Component。
+
+## schema.Message:对话的基本单位
+
+`Message` 是 Eino 里对话数据的基本结构:
+
+```go
+type Message struct {
+ Role RoleType // system / user / assistant / tool
+ Content string // 文本内容
+ ToolCalls []ToolCall // 仅 assistant 消息可能有
+ // ...
+}
+```
+
+常用构造函数:
+
+```go
+schema.SystemMessage("You are a helpful assistant.")
+schema.UserMessage("What is the weather today?")
+schema.AssistantMessage("I don't know.", nil) // 第二个参数是 ToolCalls
+schema.ToolMessage("tool result", "call_id")
+```
+
+**角色语义:**
+
+- `system`:系统指令,通常放在 messages 最前面
+- `user`:用户输入
+- `assistant`:模型回复
+- `tool`:工具调用结果(后续章节涉及)
+
+## 前置条件
+
+### 获取代码
+
+```bash
+git clone https://github.com/cloudwego/eino-examples.git
+cd eino-examples/quickstart/chatwitheino
+```
+
+- Go 版本:Go 1.21+(见 `go.mod`)
+- 一个可调用的 ChatModel(默认使用 OpenAI;也支持 Ark)
+
+### 方式 A:OpenAI(默认)
+
+```bash
+export OPENAI_API_KEY="..."
+export OPENAI_MODEL="gpt-4.1-mini" # OpenAI 2025 年新模型,也可用 gpt-4o、gpt-4o-mini 等
+# 可选:
+# OPENAI_BASE_URL(代理或兼容服务)
+# OPENAI_BY_AZURE=true(使用 Azure OpenAI)
+```
+
+### 方式 B:Ark
+
+```bash
+export MODEL_TYPE="ark"
+export ARK_API_KEY="..."
+export ARK_MODEL="..."
+# 可选:ARK_BASE_URL
+```
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+go run ./cmd/ch01 -- "用一句话解释 Eino 的 Component 设计解决了什么问题?"
+```
+
+输出示例(流式逐步打印):
+
+```
+[assistant] Eino 的 Component 设计通过定义统一接口...
+```
+
+## 入口代码做了什么
+
+按执行顺序:
+
+1. **创建 ChatModel**:根据 `MODEL_TYPE` 环境变量选择 OpenAI 或 Ark 实现
+2. **构造输入 messages**:`SystemMessage(instruction)` + `UserMessage(query)`
+3. **调用 Stream**:所有 ChatModel 实现都必须支持 `Stream()`,返回 `StreamReader[*Message]`
+4. **打印结果**:迭代 `StreamReader` 逐帧打印 assistant 回复
+
+关键代码片段(**注意:这是简化后的代码片段,不能直接运行****,完整代码请参考** [cmd/ch01/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch01/main.go)):
+
+```go
+// 构造输入
+messages := []*schema.Message{
+ schema.SystemMessage(instruction),
+ schema.UserMessage(query),
+}
+
+// 调用 Stream(所有 ChatModel 都必须实现)
+stream, err := cm.Stream(ctx, messages)
+if err != nil {
+ log.Fatal(err)
+}
+defer stream.Close()
+
+for {
+ chunk, err := stream.Recv()
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Print(chunk.Content)
+}
+```
+
+## 本章小结
+
+- **Component 接口**:定义可替换、可组合、可测试的能力边界
+- **Message**:对话数据的基本单位,通过角色区分语义
+- **ChatModel**:最基础的 Component,提供 `Generate` 和 `Stream` 两个核心方法
+- **实现选择**:通过环境变量或配置切换 OpenAI/Ark 等不同实现,业务代码无需改动
diff --git a/content/zh/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md b/content/zh/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md
new file mode 100644
index 0000000000..e7360cf64d
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_02_chatmodelagent_runner_agentevent.md
@@ -0,0 +1,309 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第二章:ChatModelAgent、Runner、AgentEvent(Console 多轮)
+weight: 2
+---
+
+本章目标:引入 ADK 的执行抽象(Agent + Runner),并用一个 Console 程序实现多轮对话。
+
+## 代码位置
+
+- 入口代码:[cmd/ch02/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch02/main.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+go run ./cmd/ch02
+```
+
+看到提示后输入问题(空行退出):
+
+```
+you> 你好,解释一下 Eino 里的 Agent 是什么?
+...
+you> 再用一句话总结一下
+...
+```
+
+## 关键概念
+
+### 从 Component 到 Agent
+
+第一章我们学习了 **Component**(组件),它是 Eino 中可替换、可组合的能力单元:
+
+- `ChatModel`:调用大语言模型
+- `Tool`:执行特定任务
+- `Retriever`:检索信息
+- `Loader`:加载数据
+
+**Component 和 Agent 的关系:**
+
+- **Component 不构成完整的 AI 应用**:它只是能力单元,需要被组织、编排、执行
+- **Agent 是完整的 AI 应用**:它封装了完整的业务逻辑,可以直接运行
+- **Agent 内部使用 Component**:最核心的是 `ChatModel`(对话能力)和 `Tool`(执行能力)
+
+**为什么需要 Agent?**
+
+如果只有 Component,你需要自己:
+
+- 管理对话历史
+- 编排调用流程(何时调用模型、何时调用工具)
+- 处理流式输出
+- 实现中断恢复
+- ...
+
+**Agent 提供了什么?**
+
+- **完整的运行时框架**:通过 `Runner` 统一管理执行过程
+- **标准的事件流输出**:`Run() -> AsyncIterator[*AgentEvent]`,支持流式、中断、恢复
+- **可扩展能力**:可以添加 tools、middleware、interrupt 等
+- **开箱即用**:创建 Agent 后直接运行,无需关心内部细节
+
+**本章示例:**
+
+`ChatModelAgent` 是最简单的 Agent,它内部只使用了 `ChatModel`,但已经具备了 Agent 的完整能力框架。后续章节会展示如何添加 `Tool` 等更多能力。
+
+### Agent 接口
+
+`Agent` 是 ADK 中的核心接口,定义了智能体的基本行为:
+
+```go
+type Agent interface {
+ Name(ctx context.Context) string
+ Description(ctx context.Context) string
+
+ // Run 执行 Agent,返回事件流
+ Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
+}
+```
+
+**接口职责:**
+
+- `Name()` / `Description()`:标识 Agent 的名称和描述
+- `Run()`:执行 Agent 的核心方法,接收输入消息,返回事件流
+
+**设计理念:**
+
+- **统一抽象**:所有 Agent(ChatModelAgent、WorkflowAgent、SupervisorAgent 等)都实现这个接口
+- **事件驱动**:通过事件流(`AsyncIterator[*AgentEvent]`)输出执行过程,支持流式响应
+- **可扩展性**:后续加入 tools、middleware、interrupt 等能力时,接口保持不变
+
+### ChatModelAgent
+
+`ChatModelAgent` 是 Agent 接口的一个实现,基于 ChatModel 构建:
+
+```go
+agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
+ Name: "Ch02ChatModelAgent",
+ Description: "A minimal ChatModelAgent with in-memory multi-turn history.",
+ Instruction: instruction,
+ Model: cm,
+})
+```
+
+**ChatModel vs ChatModelAgent:本质区别**
+
+
+| 维度 | ChatModel | ChatModelAgent |
+| 定位 | Component(组件) | Agent(智能体) |
+| 接口 | Generate() / Stream() | Run() -> AsyncIterator[*AgentEvent] |
+| 输出 | 直接返回消息内容 | 返回事件流(包含消息、控制动作等) |
+| 能力 | 单纯的模型调用 | 可扩展 tools、middleware、interrupt 等 |
+| 适用场景 | 简单的对话场景 | 复杂的智能体应用 |
+
+
+**为什么需要 ChatModelAgent?**
+
+1. **统一抽象**:ChatModel 只是 Component 的一种,而 Agent 是更高层的抽象,可以组合多种 Component
+2. **事件驱动**:Agent 输出事件流,支持流式响应、中断恢复、状态转移等复杂场景
+3. **可扩展性**:ChatModelAgent 可以添加 tools、middleware、interrupt 等能力,而 ChatModel 只能调用模型
+4. **编排友好**:Agent 可以被 Runner 统一管理,支持 checkpoint、恢复等运行时能力
+
+**简单来说:**
+
+- **ChatModel** = "负责与大语言模型通信的组件,屏蔽不同模型提供商的差异(OpenAI、Ark、Claude 等)"
+- **ChatModelAgent** = "基于模型构建的智能体,可以调用模型,但还能做更多事"
+
+**类比理解:**
+
+- **ChatModel** 就像"数据库驱动":负责与数据库通信,屏蔽 MySQL/PostgreSQL 的差异
+- **ChatModelAgent** 就像"业务逻辑层":基于数据库驱动构建,但还包含业务规则、事务管理等
+
+**特点:**
+
+- 封装了 ChatModel 的调用逻辑
+- 提供统一的 `Run() -> AgentEvent` 输出形态
+- 后续可以添加 tools、middleware 等能力
+
+### Runner
+
+`Runner` 是执行 Agent 的入口点,负责管理 Agent 的生命周期:
+
+```go
+type Runner struct {
+ a Agent // 要执行的 Agent
+ enableStreaming bool
+ store CheckPointStore // 用于中断恢复的状态存储
+}
+```
+
+**为什么需要 Runner?**
+
+虽然 Agent 提供了 `Run()` 方法,但直接调用会缺少很多运行时能力:
+
+1. **生命周期管理**:Runner 管理 Agent 的启动、恢复、中断等状态
+2. **Checkpoint 支持**:配合 `CheckPointStore` 实现中断恢复(后续章节涉及)
+3. **统一入口**:提供 `Run()` 和 `Query()` 等便捷方法
+4. **事件流封装**:将 Agent 的事件流转换为可消费的 `AsyncIterator[*AgentEvent]`
+
+**使用方式:**
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+})
+
+// 方式 1:传入消息列表
+events := runner.Run(ctx, history)
+
+// 方式 2:便捷方法,传入单个查询字符串
+events := runner.Query(ctx, "你好")
+```
+
+### AgentEvent
+
+`AgentEvent` 是 Runner 返回的事件单元:
+
+```go
+type AgentEvent struct {
+ AgentName string
+ RunPath []RunStep
+
+ Output *AgentOutput // 输出内容
+ Action *AgentAction // 控制动作
+ Err error // 执行错误
+}
+```
+
+**主要字段:**
+
+- `event.Err`:执行错误
+- `event.Output.MessageOutput`:message 或 message stream(流式)
+- `event.Action`:中断/转移/退出等控制动作(后续章节用到)
+
+### AsyncIterator:事件流的消费方式
+
+`Runner.Run()` 返回的是 `*AsyncIterator[*AgentEvent]`,这是一个非阻塞的流式迭代器。
+
+**为什么用 AsyncIterator 而不是直接返回结果?**
+
+因为 Agent 的执行是**流式**的:模型逐 token 生成回复,Tool 调用穿插其中。如果等全部完成再返回,用户需要等待更长时间。`AsyncIterator` 让你可以实时消费每一个事件。
+
+**消费方式:**
+
+```go
+// events 是 *AsyncIterator[*AgentEvent],由 runner.Run() 返回
+events := runner.Run(ctx, history)
+
+for {
+ event, ok := events.Next() // 获取下一个事件,阻塞直到有事件或结束
+ if !ok {
+ break // 迭代器关闭,全部事件已消费
+ }
+ if event.Err != nil {
+ // 处理错误
+ }
+ if event.Output != nil && event.Output.MessageOutput != nil {
+ // 处理消息输出(可能是流式)
+ }
+}
+```
+
+**注意:**每次 `runner.Run()` 创建新的迭代器,消费一次后不可重复使用。
+
+## 多轮对话的实现
+
+本章实现的是简单的多轮对话:用户输入 → 模型回复 → 用户继续输入 → ...
+
+**实现方式:**
+
+没有 tools 时,`ChatModelAgent` 在一次 `Run()` 里只会完成一轮模型调用。多轮对话是通过调用侧维护 history 实现的:
+
+1. 用 `history []*schema.Message` 保存累计对话
+2. 每次用户输入:把 `UserMessage` 追加到 history
+3. 调用 `runner.Run(ctx, history)` 得到事件流,消费得到 assistant 文本
+4. 把本轮 assistant 文本追加回 history,进入下一轮
+
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch02/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch02/main.go)):
+
+```go
+history := make([]*schema.Message, 0, 16)
+
+for {
+ // 1. 读取用户输入
+ line := readUserInput()
+ if line == "" {
+ break
+ }
+
+ // 2. 追加用户消息到 history
+ history = append(history, schema.UserMessage(line))
+
+ // 3. 调用 Runner 执行 Agent
+ events := runner.Run(ctx, history)
+
+ // 4. 消费事件流,收集 assistant 回复
+ content := collectAssistantFromEvents(events)
+
+ // 5. 追加 assistant 消息到 history
+ history = append(history, schema.AssistantMessage(content, nil))
+}
+```
+
+**流程图:**
+
+```
+┌─────────────────────────────────────────┐
+│ 初始化 history = [] │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 用户输入 UserMessage │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 追加到 history │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.Run(history) │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 消费事件流 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 追加 AssistantMessage│
+ └──────────────────────┘
+ ↓
+ (循环继续)
+```
+
+## 本章小结
+
+- **Agent 接口**:定义智能体的基本行为,核心是 `Run() -> AsyncIterator[*AgentEvent]`
+- **ChatModelAgent**:基于 ChatModel 实现的 Agent,提供统一的执行抽象
+- **Runner**:Agent 的执行入口,管理生命周期、checkpoint、事件流等运行时能力
+- **AgentEvent**:事件驱动的输出单元,支持流式响应和控制动作
+- **多轮对话**:通过调用侧维护 history 实现,每次 `Run()` 完成一轮对话
diff --git a/content/zh/docs/eino/quick_start/chapter_03_memory_and_session.md b/content/zh/docs/eino/quick_start/chapter_03_memory_and_session.md
new file mode 100644
index 0000000000..0eba844174
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_03_memory_and_session.md
@@ -0,0 +1,317 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第三章:Memory 与 Session(持久化对话)
+weight: 3
+---
+
+本章目标:实现对话历史的持久化存储,支持跨进程恢复会话。
+
+> **⚠️ 重要说明:业务层概念 vs 框架概念**
+
+> 本章介绍的 **Memory、Session、Store 是业务层概念**,**不是 Eino 框架的核心组件**。
+
+>
+
+> 换句话说,Eino 框架只负责"如何处理消息",而"如何存储消息"完全由业务层决定。本章提供的实现只是一个简单的参考示例,你可以根据自己的业务需求选择完全不同的存储方案(数据库、Redis、云存储等)。
+
+## 代码位置
+
+- 入口代码:[cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)
+- Memory 实现:[mem/store.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/mem/store.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 创建新会话
+go run ./cmd/ch03
+
+# 恢复已有会话
+go run ./cmd/ch03 --session
+```
+
+输出示例:
+
+```
+Created new session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+Session title: New Session
+Enter your message (empty line to exit):
+you> 你好,我是张三
+[assistant] 你好张三!很高兴认识你...
+you> 我叫什么名字?
+[assistant] 你叫张三...
+
+Session saved: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+Resume with: go run ./cmd/ch03 --session 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+```
+
+## 从内存到持久化:为什么需要 Memory
+
+第二章我们实现了多轮对话,但有一个问题:**对话历史只存在于内存中**。
+
+**内存存储的局限:**
+
+- 进程退出后,对话历史丢失
+- 无法跨设备、跨进程恢复会话
+- 无法实现会话管理(列表、删除、搜索等)
+
+**Memory 的定位:**
+
+- **Memory 是对话历史的持久化存储**:将对话保存到磁盘或数据库
+- **Memory 支持 Session 管理**:每个 Session 代表一次完整的对话
+- **Memory 与 Agent 解耦**:Agent 不关心存储细节,只关心消息列表
+
+**简单类比:**
+
+- **内存存储** = "草稿纸"(进程退出就没了)
+- **Memory** = "笔记本"(永久保存,随时翻阅)
+
+## 关键概念
+
+> **再次强调**:以下 Session、Store 等概念都是**业务层实现**,用于管理对话历史的存储。Eino 框架本身不提供这些组件,而是由业务层负责管理消息列表,然后将消息传递给 `adk.Runner` 进行处理。
+
+### Session(业务层概念)
+
+`Session` 代表一次完整的对话会话:
+
+```go
+type Session struct {
+ ID string
+ CreatedAt time.Time
+
+ messages []*schema.Message // 对话历史
+ // ...
+}
+```
+
+**核心方法:**
+
+- `Append(msg)`:追加消息到会话,并持久化
+- `GetMessages()`:获取所有消息
+- `Title()`:从第一条用户消息生成会话标题
+
+### Store(业务层概念)
+
+`Store` 管理多个 Session 的持久化存储:
+
+```go
+type Store struct {
+ dir string // 存储目录
+ cache map[string]*Session // 内存缓存
+}
+```
+
+**核心方法:**
+
+- `GetOrCreate(id)`:获取或创建 Session
+- `List()`:列出所有 Session
+- `Delete(id)`:删除 Session
+
+### JSONL 文件格式
+
+每个 Session 存储为一个 `.jsonl` 文件:
+
+```
+{"type":"session","id":"083d16da-...","created_at":"2026-03-11T10:00:00Z"}
+{"role":"user","content":"你好,我是谁?"}
+{"role":"assistant","content":"你好!我暂时不知道你是谁..."}
+{"role":"user","content":"我叫张三"}
+{"role":"assistant","content":"好的,张三,很高兴认识你!"}
+```
+
+**为什么用 JSONL?**
+
+- **简单**:每行一个 JSON 对象,易于读写
+- **可扩展**:可以追加新消息,无需重写整个文件
+- **可读性好**:可以用文本编辑器直接查看
+- **容错性强**:单行损坏不影响其他行
+
+## Memory 的实现(业务层示例)
+
+以下是一个简单的业务层实现示例,使用 JSONL 文件存储对话历史。这只是众多可能实现中的一种,你可以根据实际需求选择数据库、Redis 等其他存储方案。
+
+### 1. 创建 Store
+
+```go
+sessionDir := "./data/sessions"
+store, err := mem.NewStore(sessionDir)
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+### 2. 获取或创建 Session
+
+```go
+sessionID := "083d16da-6b13-4fe6-afb0-c45d8f490ce1"
+session, err := store.GetOrCreate(sessionID)
+if err != nil {
+ log.Fatal(err)
+}
+```
+
+### 3. 追加用户消息
+
+```go
+userMsg := schema.UserMessage("你好")
+if err := session.Append(userMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+### 4. 获取历史并调用 Agent
+
+```go
+history := session.GetMessages()
+events := runner.Run(ctx, history)
+content := collectAssistantFromEvents(events)
+```
+
+### 5. 追加助手消息
+
+```go
+assistantMsg := schema.AssistantMessage(content, nil)
+if err := session.Append(assistantMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)):
+
+```go
+// 创建或恢复 Session
+session, err := store.GetOrCreate(sessionID)
+if err != nil {
+ log.Fatal(err)
+}
+
+// 用户输入
+userMsg := schema.UserMessage(line)
+if err := session.Append(userMsg); err != nil {
+ log.Fatal(err)
+}
+
+// 调用 Agent
+history := session.GetMessages()
+events := runner.Run(ctx, history)
+content := collectAssistantFromEvents(events)
+
+// 保存助手回复
+assistantMsg := schema.AssistantMessage(content, nil)
+if err := session.Append(assistantMsg); err != nil {
+ log.Fatal(err)
+}
+```
+
+## Session 与 Agent 的关系:业务层与框架层的协作
+
+**关键理解:**
+
+- **Session 是业务层概念**:由业务代码实现和管理,负责存储和加载对话历史
+- **Agent(Runner)是框架层概念**:由 Eino 框架提供,负责处理消息并生成回复
+- **两者的交互点**:业务层通过 `session.GetMessages()` 获取消息列表,传递给 `runner.Run(ctx, history)` 进行处理
+
+**架构分层:**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 业务层(你的代码) │
+│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
+│ │ Session │───→│ GetMessages() │───→│ runner.Run() │ │
+│ │ (存储) │ │ (消息列表) │ │ (框架调用) │ │
+│ └─────────────┘ └──────────────┘ └───────────────┘ │
+│ ↑ │ │
+│ │ ↓ │
+│ ┌─────────────┐ ┌───────────────┐ │
+│ │ Append() │←─────────────────────│ 助手回复 │ │
+│ │ (保存消息) │ └───────────────┘ │
+│ └─────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ↓
+┌─────────────────────────────────────────────────────────────┐
+│ 框架层(Eino 框架) │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ adk.Runner:接收消息列表,调用 ChatModel,返回回复 │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**流程图:**
+
+```
+┌─────────────────────────────────────────┐
+│ 用户输入 │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.Append() │
+ │ 保存用户消息 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.GetMessages()│
+ │ 获取完整历史 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.Run(history) │
+ │ Agent 处理消息 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 收集助手回复 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ session.Append() │
+ │ 保存助手消息 │
+ └──────────────────────┘
+```
+
+## 本章小结
+
+**框架层 vs 业务层:**
+
+- **Eino 框架层**:提供 `adk.Runner`、`schema.Message` 等基础抽象,不关心消息如何存储
+- **业务层(本章实现)**:Memory/Session/Store 是业务层概念,用于管理对话历史的存储
+
+**业务层概念:**
+
+- **Memory**:对话历史的持久化存储,支持跨进程恢复
+- **Session**:一次完整的对话会话,包含 ID、创建时间、消息列表
+- **Store**:管理多个 Session 的存储,支持创建、获取、列表、删除
+- **JSONL 格式**:简单的文件格式,易于读写和扩展
+
+**业务层与框架层的交互:**
+
+- 业务层负责存储消息,通过 `session.GetMessages()` 获取消息列表
+- 将消息列表传递给框架层的 `runner.Run(ctx, history)` 进行处理
+- 收集框架层返回的回复,再由业务层保存到存储中
+
+> **💡 提示**:本章的实现只是众多存储方案中的一种简单示例。在实际项目中,你可以根据业务需求选择数据库、Redis、云存储等方案,甚至可以实现更复杂的功能如会话过期清理、搜索、分享等。
+
+## 扩展思考:业务层存储方案的选择
+
+本章提供的 JSONL 文件存储方案适合简单的单机应用。在实际业务中,你可能需要考虑其他存储方案:
+
+**其他存储实现:**
+
+- 数据库存储(MySQL、PostgreSQL、MongoDB)
+- Redis 存储(支持分布式)
+- 云存储(S3、OSS)
+
+**高级功能:**
+
+- 会话过期清理
+- 会话搜索
+- 会话导出/导入
+- 会话分享
diff --git a/content/zh/docs/eino/quick_start/chapter_04_tool_and_filesystem.md b/content/zh/docs/eino/quick_start/chapter_04_tool_and_filesystem.md
new file mode 100644
index 0000000000..9f0e38919c
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_04_tool_and_filesystem.md
@@ -0,0 +1,329 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第四章:Tool 与文件系统访问
+weight: 4
+---
+
+本章目标:为 Agent 添加 Tool 能力,让 Agent 能够访问文件系统。
+
+## 为什么需要 Tool
+
+前三章我们实现的 Agent 只能对话,无法执行实际操作。
+
+**Agent 的局限:**
+
+- 只能生成文本回复
+- 无法访问外部资源(文件、API、数据库等)
+- 无法执行实际任务(计算、查询、修改等)
+
+**Tool 的定位:**
+
+- **Tool 是 Agent 的能力扩展**:让 Agent 能够执行具体操作
+- **Tool 封装了具体实现**:Agent 不关心 Tool 内部如何工作,只关心输入输出
+- **Tool 可组合**:一个 Agent 可以有多个 Tool,根据需要选择调用
+
+**简单类比:**
+
+- **Agent** = "智能助手"(能理解指令,但需要工具才能执行)
+- **Tool** = "工具箱"(文件操作、网络请求、数据库查询等)
+
+## 为什么需要文件系统能力
+
+本示例是 ChatWithDoc(与文档对话),目标是帮助用户学习 Eino 框架并编写 Eino 代码。那么,最好的文档是什么?
+
+**答案就是:Eino 仓库的代码本身。**
+
+- **Code**: 源代码展示了框架的真实实现
+- **Comment**: 代码注释提供了设计思路和使用说明
+- **Examples**: 示例代码演示了最佳实践
+
+通过文件系统访问能力,Agent 可以直接读取 Eino 源码、注释和示例,为用户提供最准确、最及时的技术支持。
+
+## 关键概念
+
+### Tool 接口
+
+`Tool` 是 Eino 中定义可执行能力的接口:
+
+```go
+// BaseTool 提供工具的元信息,ChatModel 使用这些信息决定是否以及如何调用工具
+type BaseTool interface {
+ Info(ctx context.Context) (*schema.ToolInfo, error)
+}
+
+// InvokableTool 是可以被 ToolsNode 执行的工具
+type InvokableTool interface {
+ BaseTool
+ // InvokableRun 执行工具,参数是 JSON 编码的字符串,返回字符串结果
+ InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
+}
+
+// StreamableTool 是 InvokableTool 的流式变体
+type StreamableTool interface {
+ BaseTool
+ // StreamableRun 流式执行工具,返回 StreamReader
+ StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
+}
+```
+
+**接口层次:**
+
+- `BaseTool`:基础接口,只提供元信息
+- `InvokableTool`:可执行工具(继承 BaseTool)
+- `StreamableTool`:流式工具(继承 BaseTool)
+
+### Backend 接口
+
+`Backend` 是 Eino 中用于文件系统操作的抽象接口:
+
+```go
+type Backend interface {
+ // 列出目录下的文件信息
+ LsInfo(ctx context.Context, req *LsInfoRequest) ([]FileInfo, error)
+
+ // 读取文件内容,支持按行偏移和限制
+ Read(ctx context.Context, req *ReadRequest) (*FileContent, error)
+
+ // 在文件中搜索匹配的内容
+ GrepRaw(ctx context.Context, req *GrepRequest) ([]GrepMatch, error)
+
+ // 根据 glob 模式匹配文件
+ GlobInfo(ctx context.Context, req *GlobInfoRequest) ([]FileInfo, error)
+
+ // 写入文件内容
+ Write(ctx context.Context, req *WriteRequest) error
+
+ // 编辑文件内容(字符串替换)
+ Edit(ctx context.Context, req *EditRequest) error
+}
+```
+
+### LocalBackend
+
+`LocalBackend` 是 Backend 的本地文件系统实现,直接访问操作系统的文件系统:
+
+```go
+import localbk "github.com/cloudwego/eino-ext/adk/backend/local"
+
+backend, err := localbk.NewBackend(ctx, &localbk.Config{})
+```
+
+**特点:**
+
+- 直接访问本地文件系统,使用 Go 标准库实现
+- 支持所有 Backend 接口方法
+- 支持执行 shell 命令(ExecuteStreaming)
+- 路径安全:要求使用绝对路径,防止目录遍历攻击
+- 零配置:开箱即用,无需额外设置
+
+## 实现:使用 DeepAgent
+
+本章使用 DeepAgent 预构建 Agent,它提供了 Backend 和 StreamingShell 的一级配置,可以方便地注册文件系统相关的工具。
+
+### 从 ChatModelAgent 到 DeepAgent:何时需要切换?
+
+前面章节一直使用 `ChatModelAgent`,它已经能处理多轮对话。但要访问文件系统,我们需要切换到 `DeepAgent`。
+
+**ChatModelAgent vs DeepAgent 对比:**
+
+
+| 能力 | ChatModelAgent | DeepAgent |
+| 多轮对话 | ✅ | ✅ |
+| 添加自定义 Tool | ✅ 手动注册每个 Tool | ✅ 手动注册或自动注册 |
+| 文件系统访问(Backend) | ❌ 需手动创建并注册所有文件工具 | ✅ 一级配置,自动注册 |
+| 命令执行(StreamingShell) | ❌ 需手动创建 | ✅ 一级配置,自动注册 |
+| 内置任务管理 | ❌ | ✅ write_todos 工具 |
+| 支持子 Agent | ❌ | ✅ |
+
+
+**选择建议:**
+
+- 纯对话场景(无外部访问)→ 用 `ChatModelAgent`
+- 需要访问文件系统或执行命令 → 用 `DeepAgent`
+
+### 为什么使用 DeepAgent?
+
+相比直接使用 ChatModelAgent,DeepAgent 的优势:
+
+1. **一级配置**: Backend 和 StreamingShell 是一级配置,直接传入即可
+2. **自动注册工具**: 配置 Backend 后自动注册文件系统工具,无需手动创建
+3. **内置任务管理**: 提供 `write_todos` 工具,支持任务规划和跟踪
+4. **支持子 Agent**: 可以配置专门的子 Agent 处理特定任务
+5. **更强大**: 集成了文件系统、命令执行等多种能力
+
+### 代码实现
+
+```go
+import (
+ localbk "github.com/cloudwego/eino-ext/adk/backend/local"
+ "github.com/cloudwego/eino/adk/prebuilt/deep"
+)
+
+// 创建 LocalBackend
+backend, err := localbk.NewBackend(ctx, &localbk.Config{})
+
+// 创建 DeepAgent,自动注册文件系统工具
+agent, err := deep.New(ctx, &deep.Config{
+ Name: "Ch04ToolAgent",
+ Description: "ChatWithDoc agent with filesystem access via LocalBackend.",
+ ChatModel: cm,
+ Instruction: instruction,
+ Backend: backend, // 提供文件系统操作能力
+ StreamingShell: backend, // 提供命令执行能力
+ MaxIteration: 50,
+})
+```
+
+### DeepAgent 自动注册的工具
+
+当配置了 `Backend` 和 `StreamingShell` 后,DeepAgent 会自动注册以下工具:
+
+- `read_file`: 读取文件内容
+- `write_file`: 写入文件内容
+- `edit_file`: 编辑文件内容
+- `glob`: 根据 glob 模式查找文件
+- `grep`: 在文件中搜索内容
+- `execute`: 执行 shell 命令
+
+## 代码位置
+
+- 入口代码:[cmd/ch04/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch04/main.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
+
+本章还需要设置 `PROJECT_ROOT`(可选,见下方运行说明)。
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 可选:设置 Eino 核心库的根目录路径
+# 未设置时,Agent 默认使用当前工作目录(即 chatwitheino 目录)作为根目录
+# 若要让 Agent 能检索完整的 Eino 代码库,建议指向 eino 核心库根目录
+export PROJECT_ROOT=/path/to/eino
+
+# 验证路径是否正确(应该能看到 adk、components、compose 等目录)
+ls $PROJECT_ROOT
+
+go run ./cmd/ch04
+```
+
+**PROJECT_ROOT 说明:**
+
+- **不设置时**:`PROJECT_ROOT` 默认为当前工作目录(`chatwitheino` 所在目录),Agent 只能访问本示例项目的文件。这对于快速试验已足够。
+- **设置后**:指向 Eino 核心库根目录,Agent 可以检索 Eino 框架的完整代码库(核心库、扩展库、示例库)。这是 ChatWithEino 的完整使用场景。
+
+**推荐的三仓库目录结构(如要完整体验):**
+
+```
+eino/ # PROJECT_ROOT(Eino 核心库)
+├── adk/
+├── components/
+├── compose/
+├── ext/ # eino-ext(扩展组件,如 OpenAI、Ark 等实现)
+├── examples/ # eino-examples(本仓库,本示例所在位置)
+│ └── quickstart/
+│ └── chatwitheino/
+└── ...
+```
+
+可以使用 `dev_setup.sh` 脚本自动设置上述目录结构:
+
+```bash
+# 在 eino 根目录运行,自动克隆扩展库和示例库到正确位置
+bash scripts/dev_setup.sh
+```
+
+输出示例:
+
+```
+you> 列出当前目录的文件
+[assistant] 我来帮你列出当前目录的文件...
+[tool call] glob(pattern: "*")
+[tool result] 找到 5 个文件:
+- main.go
+- go.mod
+- go.sum
+- README.md
+- cmd/
+
+you> 读取 main.go 文件的内容
+[assistant] 我来读取 main.go 文件...
+[tool call] read_file(file_path: "main.go")
+[tool result] 文件内容如下:
+...
+```
+
+**注意:** 如果在运行过程中遇到 Tool 报错导致 Agent 中断,请不要 panic,这是正常现象。Tool 报错是常见的情况,例如参数错误、文件不存在等。如何优雅地处理 Tool 错误,我们将在下一章详细介绍。
+
+## Tool 调用流程
+
+当 Agent 需要调用 Tool 时:
+
+```
+┌─────────────────────────────────────────┐
+│ 用户:列出当前目录的文件 │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 分析意图 │
+ │ 决定调用 glob 工具 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 生成 Tool Call │
+ │ {"pattern": "*"} │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 执行 Tool │
+ │ glob("*") │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 返回 Tool Result │
+ │ {"files": [...]} │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 生成回复 │
+ │ "找到 5 个文件..." │
+ └──────────────────────┘
+```
+
+## 本章小结
+
+- **Tool**:Agent 的能力扩展,让 Agent 能够执行具体操作
+- **Backend**:文件系统操作的抽象接口,提供统一的文件操作能力
+- **LocalBackend**:Backend 的本地文件系统实现,直接访问操作系统文件系统
+- **DeepAgent**:预构建的高级 Agent,提供 Backend 和 StreamingShell 的一级配置
+- **自动注册工具**:配置 Backend 后自动注册文件系统工具
+- **Tool 调用流程**:Agent 分析意图 → 生成 Tool Call → 执行 Tool → 返回结果 → 生成回复
+
+## 扩展思考
+
+**其他 Tool 类型:**
+
+- HTTP Tool:调用外部 API
+- Database Tool:查询数据库
+- Calculator Tool:执行计算
+- Code Executor Tool:运行代码
+
+**其他 Backend 实现:**
+
+- 可以基于 Backend 接口实现其他存储后端
+- 例如:云存储、数据库存储等
+- LocalBackend 已经提供了完整的文件系统操作能力
+
+**自定义 Tool 创建:**
+
+如果需要创建自定义 Tool,可以使用 `utils.InferTool` 从函数自动推断。详见:
+
+- [Tool 接口文档](https://github.com/cloudwego/eino/tree/main/components/tool)
+- [Tool 创建示例](https://github.com/cloudwego/eino-examples/tree/main/components/tool)
diff --git a/content/zh/docs/eino/quick_start/chapter_05_middleware.md b/content/zh/docs/eino/quick_start/chapter_05_middleware.md
new file mode 100644
index 0000000000..80613c02e9
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_05_middleware.md
@@ -0,0 +1,447 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第五章:Middleware(中间件模式)
+weight: 5
+---
+
+本章目标:理解 Middleware 模式,实现 Tool 错误处理和 ChatModel 重试机制。
+
+## 为什么需要 Middleware
+
+第四章我们为 Agent 添加了 Tool 能力,让 Agent 能够访问文件系统。但在实际应用场景中,**Tool 报错或 ChatModel 报错是常见的现象**,例如:
+
+- **Tool 报错**:文件不存在、参数错误、权限不足等
+- **ChatModel 报错**:API 限流(429)、网络超时、服务不可用等
+
+### 问题一:Tool 错误会中断整个流程
+
+当 Tool 执行失败时,错误会直接传播到 Agent,导致整个对话中断:
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+Error: open nonexistent.txt: no such file or directory
+// 对话中断,用户需要重新开始
+```
+
+### 问题二:模型调用可能因限流失败
+
+当模型 API 返回 429(Too Many Requests)错误时,整个对话也会中断:
+
+```
+Error: rate limit exceeded (429)
+// 对话中断
+```
+
+### 期望的行为
+
+这些报错信息往往**不希望直接终止 Agent 流程**,而是希望把报错信息给到模型,由模型自动纠错进行下一轮。例如:
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] 抱歉,文件不存在。让我先列出当前目录的文件...
+[tool call] glob(pattern: "*")
+```
+
+### Middleware 的定位
+
+**Middleware 模式**可以扩展 Tool 和 ChatModel 的行为,非常适合解决这个问题:
+
+- **Middleware 是 Agent 的拦截器**:在调用前后插入自定义逻辑
+- **Middleware 可处理错误**:将错误转换为模型可理解的格式
+- **Middleware 可实现重试**:自动重试失败的操作
+- **Middleware 可组合**:多个 Middleware 可以串联使用
+
+**简单类比:**
+
+- **Agent** = "业务逻辑"
+- **Middleware** = "AOP 切面"(日志、重试、错误处理等横切关注点)
+
+## 代码位置
+
+- 入口代码:[cmd/ch05/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch05/main.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。同时,需要与第四章一样设置 `PROJECT_ROOT`:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino 核心库根目录
+```
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 设置项目根目录
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch05
+```
+
+输出示例:
+
+```
+you> 列出当前目录的文件
+[assistant] 我来帮你列出文件...
+[tool call] list_files(directory: ".")
+
+you> 读取一个不存在的文件
+[assistant] 尝试读取文件...
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] 抱歉,文件不存在...
+```
+
+## 关键概念
+
+### Middleware 接口
+
+`ChatModelAgentMiddleware` 是 Agent 的中间件接口:
+
+```go
+type ChatModelAgentMiddleware interface {
+ // BeforeAgent is called before each agent run, allowing modification of
+ // the agent's instruction and tools configuration.
+ BeforeAgent(ctx context.Context, runCtx *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error)
+
+ // BeforeModelRewriteState is called before each model invocation.
+ // The returned state is persisted to the agent's internal state and passed to the model.
+ BeforeModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
+
+ // AfterModelRewriteState is called after each model invocation.
+ // The input state includes the model's response as the last message.
+ AfterModelRewriteState(ctx context.Context, state *ChatModelAgentState, mc *ModelContext) (context.Context, *ChatModelAgentState, error)
+
+ // WrapInvokableToolCall wraps a tool's synchronous execution with custom behavior.
+ // This method is only called for tools that implement InvokableTool.
+ WrapInvokableToolCall(ctx context.Context, endpoint InvokableToolCallEndpoint, tCtx *ToolContext) (InvokableToolCallEndpoint, error)
+
+ // WrapStreamableToolCall wraps a tool's streaming execution with custom behavior.
+ // This method is only called for tools that implement StreamableTool.
+ WrapStreamableToolCall(ctx context.Context, endpoint StreamableToolCallEndpoint, tCtx *ToolContext) (StreamableToolCallEndpoint, error)
+
+ // WrapEnhancedInvokableToolCall wraps an enhanced tool's synchronous execution.
+ // This method is only called for tools that implement EnhancedInvokableTool.
+ WrapEnhancedInvokableToolCall(ctx context.Context, endpoint EnhancedInvokableToolCallEndpoint, tCtx *ToolContext) (EnhancedInvokableToolCallEndpoint, error)
+
+ // WrapEnhancedStreamableToolCall wraps an enhanced tool's streaming execution.
+ // This method is only called for tools that implement EnhancedStreamableTool.
+ WrapEnhancedStreamableToolCall(ctx context.Context, endpoint EnhancedStreamableToolCallEndpoint, tCtx *ToolContext) (EnhancedStreamableToolCallEndpoint, error)
+
+ // WrapModel wraps a chat model with custom behavior.
+ // This method is called at request time when the model is about to be invoked.
+ WrapModel(ctx context.Context, m model.BaseChatModel, mc *ModelContext) (model.BaseChatModel, error)
+}
+```
+
+**设计理念:**
+
+- **装饰器模式**:每个 Middleware 包装原始调用,可以修改输入、输出或错误
+- **洋葱模型**:请求从外向内穿过 Middleware,响应从内向外返回
+- **可组合**:多个 Middleware 按顺序执行
+
+### Middleware 执行顺序
+
+`Handlers`(即 Middlewares)按**数组正序**包装,形成洋葱模型:
+
+```go
+Handlers: []adk.ChatModelAgentMiddleware{
+ &middlewareA{}, // 最外层:最先 Wrap,最先拦截请求,但 WrapModel 最后生效
+ &middlewareB{}, // 中间层
+ &middlewareC{}, // 最内层:最后 Wrap
+}
+```
+
+**对于 Tool 调用的执行顺序:**
+
+```
+请求 → A.Wrap → B.Wrap → C.Wrap → 实际 Tool 执行 → C返回 → B返回 → A返回 → 响应
+```
+
+**实用建议:** 将 `safeToolMiddleware`(错误捕获)放在最内层(数组末尾),确保其他 Middleware 抛出的中断错误能正确向外传播。
+
+### SafeToolMiddleware
+
+`SafeToolMiddleware` 将 Tool 错误转换为字符串,让模型能够理解并处理:
+
+```go
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ // 将错误转换为字符串,而不是返回错误
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+```
+
+**效果:**
+
+```
+[tool call] read_file(file_path: "nonexistent.txt")
+[tool result] [tool error] open nonexistent.txt: no such file or directory
+[assistant] 抱歉,文件不存在,请检查文件路径...
+// 对话继续,模型可以根据错误信息调整策略
+```
+
+### ModelRetryConfig
+
+`ModelRetryConfig` 配置 ChatModel 的自动重试:
+
+```go
+type ModelRetryConfig struct {
+ MaxRetries int // 最大重试次数
+ IsRetryAble func(ctx context.Context, err error) bool // 判断是否可重试
+}
+```
+
+**使用方式(以 DeepAgent 为例):**
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ // ...
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ // 429 限流错误可重试
+ return strings.Contains(err.Error(), "429") ||
+ strings.Contains(err.Error(), "Too Many Requests") ||
+ strings.Contains(err.Error(), "qpm limit")
+ },
+ },
+})
+```
+
+**重试策略:**
+
+- 指数退避:每次重试间隔递增
+- 可配置条件:通过 `IsRetryAble` 判断哪些错误可重试
+- 自动恢复:无需用户干预
+
+## Middleware 的实现
+
+### 1. 实现 SafeToolMiddleware
+
+```go
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ // 中断错误不转换,需要继续传播
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return "", err
+ }
+ // 其他错误转换为字符串
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+```
+
+### 2. 实现流式 Tool 错误处理
+
+```go
+func (m *safeToolMiddleware) WrapStreamableToolCall(
+ _ context.Context,
+ endpoint adk.StreamableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.StreamableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (*schema.StreamReader[string], error) {
+ sr, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return nil, err
+ }
+ // 返回包含错误信息的单帧流
+ return singleChunkReader(fmt.Sprintf("[tool error] %v", err)), nil
+ }
+ // 包装流,捕获流中的错误
+ return safeWrapReader(sr), nil
+ }, nil
+}
+```
+
+### 3. 配置 Agent 使用 Middleware
+
+本章继续使用第四章引入的 `DeepAgent`,在其 `Handlers` 字段中注册 Middleware:
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ Name: "Ch05MiddlewareAgent",
+ Description: "ChatWithDoc agent with safe tool middleware and retry.",
+ ChatModel: cm,
+ Instruction: agentInstruction,
+ Backend: backend,
+ StreamingShell: backend,
+ MaxIteration: 50,
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &safeToolMiddleware{}, // 将 Tool 错误转换为字符串
+ },
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ return strings.Contains(err.Error(), "429") ||
+ strings.Contains(err.Error(), "Too Many Requests")
+ },
+ },
+})
+```
+
+**注意**:`Handlers` 字段(在配置中)和 "Middleware"(在文档中讨论的概念)是同一回事——`Handlers` 是配置字段名,而 `ChatModelAgentMiddleware` 是接口名。
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch05/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch05/main.go)):
+
+
+```go
+// SafeToolMiddleware 捕获 Tool 错误并转换为字符串
+type safeToolMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *safeToolMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ _ *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ result, err := endpoint(ctx, args, opts...)
+ if err != nil {
+ if _, ok := compose.IsInterruptRerunError(err); ok {
+ return "", err
+ }
+ return fmt.Sprintf("[tool error] %v", err), nil
+ }
+ return result, nil
+ }, nil
+}
+
+// 配置 DeepAgent(与第四章一样,新增 Handlers 和 ModelRetryConfig)
+agent, _ := deep.New(ctx, &deep.Config{
+ ChatModel: cm,
+ Backend: backend,
+ StreamingShell: backend,
+ MaxIteration: 50,
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &safeToolMiddleware{},
+ },
+ ModelRetryConfig: &adk.ModelRetryConfig{
+ MaxRetries: 5,
+ IsRetryAble: func(_ context.Context, err error) bool {
+ return strings.Contains(err.Error(), "429")
+ },
+ },
+})
+```
+
+## Middleware 执行流程
+
+```
+┌─────────────────────────────────────────┐
+│ 用户:读取不存在的文件 │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 分析意图 │
+ │ 决定调用 read_file │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ SafeToolMiddleware │
+ │ 拦截 Tool 调用 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 执行 read_file │
+ │ 返回错误 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ SafeToolMiddleware │
+ │ 将错误转换为字符串 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 返回 Tool Result │
+ │ "[tool error] ..." │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 生成回复 │
+ │ "抱歉,文件不存在..." │
+ └──────────────────────┘
+```
+
+## 本章小结
+
+- **Middleware**:Agent 的拦截器,可以在调用前后插入自定义逻辑
+- **SafeToolMiddleware**:将 Tool 错误转换为字符串,让模型能够理解并处理
+- **ModelRetryConfig**:配置 ChatModel 的自动重试,处理限流等临时错误
+- **装饰器模式**:Middleware 包装原始调用,可以修改输入、输出或错误
+- **洋葱模型**:请求从外向内穿过 Middleware,响应从内向外返回
+
+## 扩展思考
+
+**Eino 内置 Middleware:**
+
+
+| Middleware | 功能说明 |
+| reduction | 工具输出缩减,当工具返回内容过长时自动截断并卸载到文件系统,防止上下文溢出 |
+| summarization | 对话历史自动摘要,当 token 数量超过阈值时自动生成摘要压缩历史 |
+| skill | 技能加载中间件,让 Agent 能够动态加载和执行预定义的技能 |
+
+
+**Middleware 链示例:**
+
+```go
+import (
+ "github.com/cloudwego/eino/adk/middlewares/reduction"
+ "github.com/cloudwego/eino/adk/middlewares/summarization"
+ "github.com/cloudwego/eino/adk/middlewares/skill"
+)
+
+// 创建 reduction middleware:管理工具输出长度
+reductionMW, _ := reduction.New(ctx, &reduction.Config{
+ Backend: filesystemBackend, // 存储后端
+ MaxLengthForTrunc: 50000, // 单次工具输出最大长度
+ MaxTokensForClear: 30000, // 触发清理的 token 阈值
+})
+
+// 创建 summarization middleware:自动压缩对话历史
+summarizationMW, _ := summarization.New(ctx, &summarization.Config{
+ Model: chatModel, // 用于生成摘要的模型
+ Trigger: &summarization.TriggerCondition{
+ ContextTokens: 190000, // 触发摘要的 token 阈值
+ },
+})
+
+// 组合多个 middleware(概念示例,使用 DeepAgent 时将 adk.NewChatModelAgent 替换为 deep.New)
+agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
+ Handlers: []adk.ChatModelAgentMiddleware{ // 注意:配置字段名为 Handlers,概念上与 Middlewares 等价
+ summarizationMW, // 最外层:对话历史摘要
+ reductionMW, // 中间层:工具输出缩减
+ },
+})
+```
diff --git a/content/zh/docs/eino/quick_start/chapter_06_callback_and_trace.md b/content/zh/docs/eino/quick_start/chapter_06_callback_and_trace.md
new file mode 100644
index 0000000000..0ff7dfadc2
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_06_callback_and_trace.md
@@ -0,0 +1,367 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第六章:Callback 与 Trace(可观测性)
+weight: 6
+---
+
+本章目标:理解 Callback 机制,集成 CozeLoop 实现链路追踪和可观测性。
+
+## 代码位置
+
+- 入口代码:[cmd/ch06/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch06/main.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。同时,需要与第四章一样设置 `PROJECT_ROOT`:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino 核心库根目录(不设置则默认使用当前目录)
+```
+
+可选:配置 CozeLoop 实现链路追踪:
+
+```bash
+export COZELOOP_WORKSPACE_ID=your_workspace_id
+export COZELOOP_API_TOKEN=your_token
+```
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 设置项目根目录
+export PROJECT_ROOT=/path/to/your/project
+
+# 可选:配置 CozeLoop
+export COZELOOP_WORKSPACE_ID=your_workspace_id
+export COZELOOP_API_TOKEN=your_token
+
+go run ./cmd/ch06
+```
+
+输出示例:
+
+```
+[trace] starting session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
+you> 你好
+[trace] chat_model_generate: model=gpt-4.1-mini tokens=150
+[trace] tool_call: name=list_files duration=23ms
+[assistant] 你好!有什么我可以帮助你的吗?
+```
+
+## 从黑盒到白盒:为什么需要 Callback
+
+前几章我们实现的 Agent 是一个"黑盒":输入问题,输出答案,但中间发生了什么我们并不清楚。
+
+**黑盒的问题:**
+
+- 不知道模型调用了多少次
+- 不知道 Tool 执行了多长时间
+- 不知道 Token 消耗了多少
+- 出问题时难以定位原因
+
+**Callback 的定位:**
+
+- **Callback 是 Eino 的旁路机制**:从 component 到 compose(下文详谈)到 adk,一以贯之
+- **Callback 在固定点位触发**:组件生命周期的 5 个关键时机
+- **Callback 可抽取实时信息**:输入、输出、错误、流式数据等
+- **Callback 用途广泛**:观测、日志、指标、追踪、调试、审计等
+
+**简单类比:**
+
+- **Agent** = "业务逻辑"(主路)
+- **Callback** = "旁路钩子"(在固定点位抽取信息)
+
+## 关键概念
+
+### Handler 接口
+
+`Handler` 是 Eino 中定义回调处理器的核心接口:
+
+```go
+type Handler interface {
+ // 非流式输入(组件开始处理前)
+ OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context
+
+ // 非流式输出(组件成功返回后)
+ OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context
+
+ // 错误(组件返回错误时)
+ OnError(ctx context.Context, info *RunInfo, err error) context.Context
+
+ // 流式输入(组件接收流式输入时)
+ OnStartWithStreamInput(ctx context.Context, info *RunInfo,
+ input *schema.StreamReader[CallbackInput]) context.Context
+
+ // 流式输出(组件返回流式输出时)
+ OnEndWithStreamOutput(ctx context.Context, info *RunInfo,
+ output *schema.StreamReader[CallbackOutput]) context.Context
+}
+```
+
+**设计理念:**
+
+- **旁路机制**:不干扰主流程,在固定点位抽取信息
+- **全流程覆盖**:从 component 到 compose 到 adk,所有组件都支持
+- **状态传递**:同一 Handler 的 OnStart→OnEnd 可通过 context 传递状态
+- **性能优化**:实现 `TimingChecker` 接口可跳过不需要的时机
+
+**RunInfo 结构:**
+
+```go
+type RunInfo struct {
+ Name string // 业务名称(节点名或用户指定)
+ Type string // 实现类型(如 "OpenAI")
+ Component string // 组件类型(如 "ChatModel")
+}
+```
+
+**重要提示:**
+
+- 流式回调必须关闭 StreamReader,否则会导致 goroutine 泄漏
+- 不要修改 Input/Output,它们被所有下游共享
+- RunInfo 可能为 nil,使用前需要检查
+
+### CozeLoop
+
+CozeLoop 是字节跳动开源的 AI 应用可观测性平台,提供了:
+
+- **链路追踪**:完整的调用链路可视化
+- **指标监控**:延迟、Token 消耗、错误率等
+- **日志聚合**:集中管理所有日志
+- **调试支持**:在线查看和调试
+
+**集成方式:**
+
+```go
+import (
+ clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"
+ "github.com/cloudwego/eino/callbacks"
+ "github.com/coze-dev/cozeloop-go"
+)
+
+// 创建 CozeLoop 客户端
+client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(apiToken),
+ cozeloop.WithWorkspaceID(workspaceID),
+)
+
+// 注册为全局 Callback
+callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+```
+
+### Callback 的触发时机
+
+Callback 在组件生命周期的 5 个关键时机触发。下表中 `Timing*` 是 Eino 内部常量名(用于 `TimingChecker` 接口),对应的 Handler 接口方法是右侧所示:
+
+
+| 时机常量 | 对应 Handler 方法 | 触发点 | 输入/输出 |
+TimingOnStart | OnStart | 组件开始处理前 | CallbackInput |
+TimingOnEnd | OnEnd | 组件成功返回后 | CallbackOutput |
+TimingOnError | OnError | 组件返回错误时 | error |
+TimingOnStartWithStreamInput | OnStartWithStreamInput | 组件接收流式输入时 | StreamReader[CallbackInput] |
+TimingOnEndWithStreamOutput | OnEndWithStreamOutput | 组件返回流式输出时 | StreamReader[CallbackOutput] |
+
+
+**示例:ChatModel 调用流程**
+
+```
+┌─────────────────────────────────────────┐
+│ ChatModel.Generate(ctx, messages) │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnStart │ ← 输入: CallbackInput (messages)
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 模型处理 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnEnd │ ← 输出: CallbackOutput (response)
+ └──────────────────────┘
+```
+
+**示例:流式输出流程**
+
+```
+┌─────────────────────────────────────────┐
+│ ChatModel.Stream(ctx, messages) │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnStart │ ← 输入: CallbackInput (messages)
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 模型处理(流式) │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ OnEndWithStreamOutput │ ← 输出: StreamReader[CallbackOutput]
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 逐个 chunk 返回 │
+ └──────────────────────┘
+```
+
+**注意:**
+
+- 流式错误(stream 中途出错)不会触发 OnError,而是在 StreamReader 中返回
+- 同一 Handler 的 OnStart→OnEnd 可通过 context 传递状态
+- 不同 Handler 之间没有执行顺序保证
+
+## Callback 的实现
+
+### 1. 实现自定义 Callback Handler
+
+完整实现 `Handler` 接口需要实现所有 5 个方法,较为繁琐。Eino 提供了 `callbacks.HandlerHelper` 帮助类来简化实现:
+
+```go
+import "github.com/cloudwego/eino/callbacks"
+
+// 使用 NewHandlerHelper 注册感兴趣的回调
+handler := callbacks.NewHandlerHelper().
+ OnStart(func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
+ log.Printf("[trace] %s/%s start", info.Component, info.Name)
+ return ctx
+ }).
+ OnEnd(func(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {
+ log.Printf("[trace] %s/%s end", info.Component, info.Name)
+ return ctx
+ }).
+ OnError(func(ctx context.Context, info *callbacks.RunInfo, err error) context.Context {
+ log.Printf("[trace] %s/%s error: %v", info.Component, info.Name, err)
+ return ctx
+ }).
+ Handler()
+
+// 注册为全局 Callback
+callbacks.AppendGlobalHandlers(handler)
+```
+
+**注意**:`RunInfo` 可能为 `nil`(如顶层调用没有 RunInfo),使用前请检查。
+
+### 2. 集成 CozeLoop
+
+```go
+func setupCozeLoop(ctx context.Context) (*cozeloop.Client, error) {
+ apiToken := os.Getenv("COZELOOP_API_TOKEN")
+ workspaceID := os.Getenv("COZELOOP_WORKSPACE_ID")
+
+ if apiToken == "" || workspaceID == "" {
+ return nil, nil // 未配置则跳过
+ }
+
+ client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(apiToken),
+ cozeloop.WithWorkspaceID(workspaceID),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // 注册为全局 Callback
+ callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+
+ return client, nil
+}
+```
+
+### 3. 在 main 中使用
+
+```go
+func main() {
+ ctx := context.Background()
+
+ // 设置 CozeLoop(可选)
+ client, err := setupCozeLoop(ctx)
+ if err != nil {
+ log.Printf("cozeloop setup failed: %v", err)
+ }
+ if client != nil {
+ defer func() {
+ time.Sleep(5 * time.Second) // 等待数据上报
+ client.Close(ctx)
+ }()
+ }
+
+ // 创建 Agent 并运行...
+}
+```
+
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch06/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch06/main.go)):
+
+```go
+// 设置 CozeLoop 追踪
+cozeloopApiToken := os.Getenv("COZELOOP_API_TOKEN")
+cozeloopWorkspaceID := os.Getenv("COZELOOP_WORKSPACE_ID")
+if cozeloopApiToken != "" && cozeloopWorkspaceID != "" {
+ client, err := cozeloop.NewClient(
+ cozeloop.WithAPIToken(cozeloopApiToken),
+ cozeloop.WithWorkspaceID(cozeloopWorkspaceID),
+ )
+ if err != nil {
+ log.Fatalf("cozeloop.NewClient failed: %v", err)
+ }
+ defer func() {
+ time.Sleep(5 * time.Second)
+ client.Close(ctx)
+ }()
+ callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
+}
+```
+
+## 可观测性的价值
+
+### 1. 性能分析
+
+通过 Callback 收集的数据,可以分析:
+
+- 模型调用延迟分布
+- Tool 执行时间排行
+- Token 消耗趋势
+
+### 2. 错误追踪
+
+当 Agent 出现问题时:
+
+- 查看完整的调用链路
+- 定位是哪个环节出错
+- 分析错误原因
+
+### 3. 成本优化
+
+通过 Token 消耗数据:
+
+- 识别高消耗的对话
+- 优化 Prompt 减少 Token
+- 选择更经济的模型
+
+## 本章小结
+
+- **Callback**:Eino 的观测钩子,在关键节点触发回调
+- **CozeLoop**:字节跳动的 AI 应用可观测性平台
+- **全局注册**:通过 `callbacks.AppendGlobalHandlers` 注册全局 Callback
+- **非侵入式**:业务代码不需要修改,Callback 自动触发
+- **可观测性价值**:性能分析、错误追踪、成本优化
+
+## 扩展思考
+
+**其他 Callback 实现:**
+
+- OpenTelemetry Callback:对接标准可观测性协议
+- 自定义日志 Callback:记录到本地文件
+- 指标 Callback:对接 Prometheus 等监控系统
+
+**高级用法:**
+
+- 在 Callback 中实现采样(只记录部分请求)
+- 在 Callback 中实现限流(根据 Token 消耗)
+- 在 Callback 中实现告警(错误率过高时通知)
diff --git a/content/zh/docs/eino/quick_start/chapter_07_interrupt_resume.md b/content/zh/docs/eino/quick_start/chapter_07_interrupt_resume.md
new file mode 100644
index 0000000000..916bf01fe1
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_07_interrupt_resume.md
@@ -0,0 +1,355 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第七章:Interrupt/Resume(中断与恢复)
+weight: 7
+---
+
+本章目标:理解 Interrupt/Resume 机制,实现 Tool 审批流程,让用户在敏感操作前进行确认。
+
+## 代码位置
+
+- 入口代码:[cmd/ch07/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch07/main.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。同时,需要与第四章一样设置 `PROJECT_ROOT`:
+
+```bash
+export PROJECT_ROOT=/path/to/eino # Eino 核心库根目录(不设置则默认使用当前目录)
+```
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 设置项目根目录
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch07
+```
+
+输出示例:
+
+```
+you> 请执行命令 echo hello
+
+⚠️ Approval Required ⚠️
+Tool: execute
+Arguments: {"command":"echo hello"}
+
+Approve this action? (y/n): y
+[tool result] hello
+
+hello
+```
+
+## 从自动执行到人工审批:为什么需要 Interrupt
+
+前几章我们实现的 Agent 会自动执行所有 Tool 调用,但在某些场景下这是危险的:
+
+**自动执行的风险:**
+
+- 删除文件:误删重要数据
+- 发送邮件:发送错误内容
+- 执行命令:执行危险操作
+- 修改配置:破坏系统设置
+
+**Interrupt 的定位:**
+
+- **Interrupt 是 Agent 的暂停机制**:在关键操作前暂停,等待用户确认
+- **Interrupt 可携带信息**:向用户展示即将执行的操作
+- **Interrupt 可恢复**:用户确认后继续执行,拒绝后返回错误
+
+**简单类比:**
+
+- **自动执行** = "自动驾驶"(完全信任系统)
+- **Interrupt** = "人工接管"(关键决策由人来做)
+
+## 关键概念
+
+### Interrupt 机制
+
+`Interrupt` 是 Eino 中实现人机协作的核心机制。
+
+**核心思想:在执行关键操作前暂停,等待用户确认后继续。**
+
+一个需要审批的 Tool 的执行被分成**两个阶段**:
+
+1. **第一次调用(触发中断)**:Tool 保存当前参数,然后返回一个中断信号。Runner 暂停执行,向调用侧返回 Interrupt 事件。
+2. **用户审批后恢复(Resume)**:Runner 重新调用 Tool,此时 Tool 检测到"已中断过",直接读取用户的审批结果并执行(或拒绝)。
+
+**简化版伪代码:**
+
+```
+func myTool(ctx, args):
+ if 第一次调用:
+ 保存 args
+ return 中断信号 // Runner 暂停,展示审批提示
+ else: // Resume 后的第二次调用
+ if 用户批准:
+ return 执行操作(保存的 args)
+ else:
+ return "操作被用户拒绝"
+```
+
+**完整代码及关键字段说明:**
+
+```go
+// 在 Tool 中触发中断
+func myTool(ctx context.Context, args string) (string, error) {
+ // wasInterrupted: 是否是 Resume 后的第二次调用(第一次为 false,Resume 后为 true)
+ // storedArgs: 第一次调用时通过 StatefulInterrupt 保存的参数,Resume 后可取回
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+
+ if !wasInterrupted {
+ // 第一次调用:触发中断,同时保存 args 供 Resume 后使用
+ return "", tool.StatefulInterrupt(ctx, &ApprovalInfo{
+ ToolName: "my_tool",
+ ArgumentsInJSON: args,
+ }, args) // 第三个参数是要保存的状态(Resume 后通过 storedArgs 取回)
+ }
+
+ // Resume 后的第二次调用:读取用户审批结果
+ // isTarget: 本次 Resume 是否针对当前 Tool(一次 Resume 只针对一个 Tool)
+ // hasData: Resume 时是否携带了审批结果数据
+ // data: 用户传入的审批结果
+ isTarget, hasData, data := tool.GetResumeContext[*ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return doSomething(storedArgs) // 使用保存的参数执行实际操作
+ }
+ return "Operation rejected by user", nil
+ }
+
+ // 其他情况(isTarget=false 意味着本次 Resume 目标不是当前 Tool):重新中断
+ return "", tool.StatefulInterrupt(ctx, &ApprovalInfo{
+ ToolName: "my_tool",
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+}
+```
+
+### ApprovalMiddleware
+
+`ApprovalMiddleware` 是一个通用的审批中间件,可以拦截特定 Tool 的调用:
+
+```go
+type approvalMiddleware struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (m *approvalMiddleware) WrapInvokableToolCall(
+ _ context.Context,
+ endpoint adk.InvokableToolCallEndpoint,
+ tCtx *adk.ToolContext,
+) (adk.InvokableToolCallEndpoint, error) {
+ // 只拦截需要审批的 Tool
+ if tCtx.Name != "execute" {
+ return endpoint, nil
+ }
+
+ return func(ctx context.Context, args string, opts ...tool.Option) (string, error) {
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+
+ if !wasInterrupted {
+ return "", tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: args,
+ }, args)
+ }
+
+ isTarget, hasData, data := tool.GetResumeContext[*commontool.ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return endpoint(ctx, storedArgs, opts...)
+ }
+ if data.DisapproveReason != nil {
+ return fmt.Sprintf("tool '%s' disapproved: %s", tCtx.Name, *data.DisapproveReason), nil
+ }
+ return fmt.Sprintf("tool '%s' disapproved", tCtx.Name), nil
+ }
+
+ // 重新中断
+ return "", tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+ }, nil
+}
+
+func (m *approvalMiddleware) WrapStreamableToolCall(
+ _ context.Context,
+ endpoint adk.StreamableToolCallEndpoint,
+ tCtx *adk.ToolContext,
+) (adk.StreamableToolCallEndpoint, error) {
+ // 如果 agent 配置了 StreamingShell,则 execute 会走流式调用,需要实现该方法才能拦截到
+ if tCtx.Name != "execute" {
+ return endpoint, nil
+ }
+ return func(ctx context.Context, args string, opts ...tool.Option) (*schema.StreamReader[string], error) {
+ wasInterrupted, _, storedArgs := tool.GetInterruptState[string](ctx)
+ if !wasInterrupted {
+ return nil, tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: args,
+ }, args)
+ }
+
+ isTarget, hasData, data := tool.GetResumeContext[*commontool.ApprovalResult](ctx)
+ if isTarget && hasData {
+ if data.Approved {
+ return endpoint(ctx, storedArgs, opts...)
+ }
+ if data.DisapproveReason != nil {
+ return singleChunkReader(fmt.Sprintf("tool '%s' disapproved: %s", tCtx.Name, *data.DisapproveReason)), nil
+ }
+ return singleChunkReader(fmt.Sprintf("tool '%s' disapproved", tCtx.Name)), nil
+ }
+
+ isTarget, _, _ = tool.GetResumeContext[any](ctx)
+ if !isTarget {
+ return nil, tool.StatefulInterrupt(ctx, &commontool.ApprovalInfo{
+ ToolName: tCtx.Name,
+ ArgumentsInJSON: storedArgs,
+ }, storedArgs)
+ }
+
+ return endpoint(ctx, storedArgs, opts...)
+ }, nil
+}
+```
+
+### CheckPointStore
+
+`CheckPointStore` 是实现中断恢复的关键组件:
+
+```go
+type CheckPointStore interface {
+ // 保存检查点
+ Put(ctx context.Context, key string, checkpoint *Checkpoint) error
+
+ // 获取检查点
+ Get(ctx context.Context, key string) (*Checkpoint, error)
+}
+```
+
+**为什么需要 CheckPointStore?**
+
+- 中断时保存状态:Tool 参数、执行位置等
+- 恢复时加载状态:从中断点继续执行
+- 支持跨进程恢复:进程重启后仍可恢复
+
+## Interrupt/Resume 的实现
+
+### 1. 配置 Runner 使用 CheckPointStore
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+ CheckPointStore: adkstore.NewInMemoryStore(), // 内存存储
+})
+```
+
+### 2. 配置 Agent 使用 ApprovalMiddleware
+
+```go
+agent, err := deep.New(ctx, &deep.Config{
+ // ... 其他配置
+ Handlers: []adk.ChatModelAgentMiddleware{
+ &approvalMiddleware{}, // 添加审批中间件
+ &safeToolMiddleware{}, // 将 Tool 错误转换为字符串(中断类错误会继续向上抛出)
+ },
+})
+```
+
+### 3. 处理中断事件
+
+```go
+checkPointID := sessionID
+
+events := runner.Run(ctx, history, adk.WithCheckPointID(checkPointID))
+content, interruptInfo, err := printAndCollectAssistantFromEvents(events)
+if err != nil {
+ return err
+}
+
+if interruptInfo != nil {
+ // 注意:建议使用同一个 stdin reader 同时读取「用户输入」与「审批 y/n」
+ // 避免审批输入被当成下一轮 you> 的消息
+ content, err = handleInterrupt(ctx, runner, checkPointID, interruptInfo, reader)
+ if err != nil {
+ return err
+ }
+}
+
+_ = session.Append(schema.AssistantMessage(content, nil))
+```
+
+## Interrupt/Resume 执行流程
+
+```
+┌─────────────────────────────────────────┐
+│ 用户:执行命令 echo hello │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 分析意图 │
+ │ 决定调用 execute │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ ApprovalMiddleware │
+ │ 拦截 Tool 调用 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 触发 Interrupt │
+ │ 保存状态到 Store │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 返回 Interrupt 事件 │
+ │ 等待用户审批 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 用户输入 y/n │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ runner.ResumeWith... │
+ │ 恢复执行 │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 执行 execute │
+ │ 或返回拒绝信息 │
+ └──────────────────────┘
+```
+
+## 本章小结
+
+- **Interrupt**:Agent 的暂停机制,在关键操作前暂停等待确认
+- **Resume**:恢复执行,用户确认后继续或拒绝后返回错误
+- **ApprovalMiddleware**:通用审批中间件,拦截特定 Tool 调用
+- **CheckPointStore**:保存中断状态,支持跨进程恢复
+- **人机协作**:关键决策由人来确认,提高安全性
+
+## 扩展思考
+
+**其他 Interrupt 场景:**
+
+- 多选项审批:用户选择多个选项之一
+- 参数补全:用户提供缺失的参数
+- 条件分支:用户决定执行路径
+
+**审批策略:**
+
+- 白名单:只审批敏感操作
+- 黑名单:审批所有操作,除了安全的
+- 动态规则:根据参数内容决定是否审批
diff --git a/content/zh/docs/eino/quick_start/chapter_08_graph_tool.md b/content/zh/docs/eino/quick_start/chapter_08_graph_tool.md
new file mode 100644
index 0000000000..c7562e9d06
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_08_graph_tool.md
@@ -0,0 +1,313 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第八章:Graph Tool(复杂工作流)
+weight: 8
+---
+
+本章目标:理解 Graph Tool 的概念,实现大文件的并行 chunk 召回,引入 compose 包构建复杂工作流。
+
+## 代码位置
+
+- 入口代码:[cmd/ch08/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch08/main.go)
+- RAG 实现:[rag/rag.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/rag/rag.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+# 设置项目根目录
+export PROJECT_ROOT=/path/to/your/project
+
+go run ./cmd/ch08
+```
+
+输出示例:
+
+```
+you> 请帮我分析 RFC6455 文档中关于 WebSocket 握手的部分
+[assistant] 我来帮你分析文档...
+[tool call] answer_from_document(file_path: "rfc6455.txt", question: "WebSocket 握手过程")
+[tool result] 找到 3 个相关片段,正在生成答案...
+[assistant] 根据 RFC6455 文档,WebSocket 握手过程如下...
+```
+
+## 从简单 Tool 到 Graph Tool:为什么需要复杂工作流
+
+第四章我们创建了简单的 Tool,每个 Tool 执行单一任务。但实际场景中,很多任务需要多个步骤协同完成。
+
+**简单 Tool 的局限:**
+
+- 单一职责:每个 Tool 只做一件事
+- 无法并行:多个独立任务无法同时执行
+- 难以复用:复杂逻辑难以拆分和组合
+
+**重要说明:本章只是展示 compose/graph/workflow 能力的一角。**
+
+从更大的视角看,Eino 的 `compose` 包提供了非常通用、确定性的编排能力:你可以把任何需要"确定性业务流程"的系统,用 `compose` 的 Graph/Chain/Workflow 组织成可执行的流水线,并且它能够**原生编排 Eino 的所有 component**(如 ChatModel、Prompt、Tools、Retriever、Embedding、Indexer 等),同时具备完整的 **callback** 体系,以及 **interrupt/resume + checkpoint** 支持。
+
+**Graph Tool 的定位:**
+
+- **Graph Tool 是 compose 工作流的 Tool 化封装**:把 `compose.Graph / compose.Chain / compose.Workflow` 这类可编译的编排产物,包装成一个 Agent 可调用的 Tool
+- **支持并行/分支/组合**:由 compose 提供(并行、分支、字段映射、子图等),Graph Tool 只是把它们暴露为 Tool 入口
+- **支持状态管理与持久化**:节点间传递数据、以及通过 checkpoint 保存/恢复运行状态
+- **可中断恢复**:既支持工作流内部的中断(节点里触发 interrupt),也支持工具层面的中断包装(嵌套 interrupt 场景)
+
+**简单类比:**
+
+- **简单 Tool** = "单步操作"(读取文件)
+- **Graph Tool** = "流水线"(读取 → 分块 → 评分 → 筛选 → 生成答案)
+
+## 关键概念
+
+### compose.Workflow
+
+`compose.Workflow` 是 Eino 中构建工作流的核心组件:
+
+```go
+wf := compose.NewWorkflow[Input, Output]()
+
+// 添加节点
+wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
+wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
+wf.AddLambdaNode("score", scoreFunc).AddInput("chunk")
+wf.AddLambdaNode("answer", answerFunc).AddInput("score")
+
+// 连接到结束节点
+wf.End().AddInput("answer")
+```
+
+**核心概念:**
+
+- **Node**:工作流中的处理单元
+- **Edge**:节点间的数据流向
+- **START**:工作流的入口
+- **END**:工作流的出口
+
+### BatchNode
+
+`BatchNode` 用于并行处理多个任务:
+
+```go
+scorer := batch.NewBatchNode(&batch.NodeConfig[Task, Result]{
+ Name: "ChunkScorer",
+ InnerTask: scoreOneChunk, // 单个任务的处理函数
+ MaxConcurrency: 5, // 最大并发数
+})
+```
+
+**工作原理:**
+
+1. 接收任务列表作为输入
+2. 并行执行每个任务(受 MaxConcurrency 限制)
+3. 收集所有结果返回
+
+### FieldMapping
+
+`FieldMapping` 用于跨节点传递数据:
+
+```go
+wf.AddLambdaNode("answer", answerFunc).
+ AddInputWithOptions("filter", // 从 filter 节点获取数据
+ []*compose.FieldMapping{compose.ToField("TopK")},
+ compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, // 从 START 节点获取数据
+ []*compose.FieldMapping{compose.MapFields("Question", "Question")},
+ compose.WithNoDirectDependency())
+```
+
+**为什么需要 FieldMapping?**
+
+- 非相邻节点间传递数据
+- 多个数据源合并到同一节点
+- 数据字段重命名
+
+## Graph Tool 的实现
+
+### 1. 定义输入输出结构
+
+```go
+type Input struct {
+ FilePath string `json:"file_path" jsonschema:"description=Absolute path to the document"`
+ Question string `json:"question" jsonschema:"description=The question to answer"`
+}
+
+type Output struct {
+ Answer string `json:"answer"`
+ Sources []string `json:"sources"`
+}
+```
+
+### 2. 构建工作流
+
+```go
+func buildWorkflow(cm model.BaseChatModel) *compose.Workflow[Input, Output] {
+ wf := compose.NewWorkflow[Input, Output]()
+
+ // load: 读取文件
+ wf.AddLambdaNode("load", compose.InvokableLambda(
+ func(ctx context.Context, in Input) ([]*schema.Document, error) {
+ data, err := os.ReadFile(in.FilePath)
+ if err != nil {
+ return nil, err
+ }
+ return []*schema.Document{{Content: string(data)}}, nil
+ },
+ )).AddInput(compose.START)
+
+ // chunk: 分块
+ wf.AddLambdaNode("chunk", compose.InvokableLambda(
+ func(ctx context.Context, docs []*schema.Document) ([]*schema.Document, error) {
+ var out []*schema.Document
+ for _, d := range docs {
+ out = append(out, splitIntoChunks(d.Content, 800)...)
+ }
+ return out, nil
+ },
+ )).AddInput("load")
+
+ // score: 并行评分
+ scorer := batch.NewBatchNode(&batch.NodeConfig[scoreTask, scoredChunk]{
+ Name: "ChunkScorer",
+ InnerTask: newScoreWorkflow(cm),
+ MaxConcurrency: 5,
+ })
+
+ wf.AddLambdaNode("score", compose.InvokableLambda(
+ func(ctx context.Context, in scoreIn) ([]scoredChunk, error) {
+ tasks := make([]scoreTask, len(in.Chunks))
+ for i, c := range in.Chunks {
+ tasks[i] = scoreTask{Text: c.Content, Question: in.Question}
+ }
+ return scorer.Invoke(ctx, tasks)
+ },
+ )).
+ AddInputWithOptions("chunk", []*compose.FieldMapping{compose.ToField("Chunks")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+ // filter: 筛选 top-k
+ wf.AddLambdaNode("filter", compose.InvokableLambda(
+ func(ctx context.Context, scored []scoredChunk) ([]scoredChunk, error) {
+ sort.Slice(scored, func(i, j int) bool {
+ return scored[i].Score > scored[j].Score
+ })
+ // 返回 top-3
+ if len(scored) > 3 {
+ scored = scored[:3]
+ }
+ return scored, nil
+ },
+ )).AddInput("score")
+
+ // answer: 生成答案
+ wf.AddLambdaNode("answer", compose.InvokableLambda(
+ func(ctx context.Context, in synthIn) (Output, error) {
+ return synthesize(ctx, cm, in)
+ },
+ )).
+ AddInputWithOptions("filter", []*compose.FieldMapping{compose.ToField("TopK")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+ wf.End().AddInput("answer")
+
+ return wf
+}
+```
+
+### 3. 封装为 Tool
+
+```go
+func BuildTool(ctx context.Context, cm model.BaseChatModel) (tool.BaseTool, error) {
+ wf := buildWorkflow(cm)
+ return graphtool.NewInvokableGraphTool[Input, Output](
+ wf,
+ "answer_from_document",
+ "Search a large document for relevant content and synthesize an answer.",
+ )
+}
+```
+
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [rag/rag.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/rag/rag.go)):
+
+```go
+// 构建工作流
+wf := compose.NewWorkflow[Input, Output]()
+
+// 添加节点
+wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
+wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
+wf.AddLambdaNode("score", scoreFunc).
+ AddInputWithOptions("chunk", []*compose.FieldMapping{compose.ToField("Chunks")}, compose.WithNoDirectDependency()).
+ AddInputWithOptions(compose.START, []*compose.FieldMapping{compose.MapFields("Question", "Question")}, compose.WithNoDirectDependency())
+
+// 封装为 Tool
+return graphtool.NewInvokableGraphTool[Input, Output](wf, "answer_from_document", "...")
+```
+
+## Graph Tool 执行流程
+
+```
+┌─────────────────────────────────────────┐
+│ 输入:file_path, question │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ load: 读取文件 │
+ │ 输出: []*Document │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ chunk: 分块 │
+ │ 输出: []*Document │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ score: 并行评分 │
+ │ (MaxConcurrency=5) │
+ │ 输出: []scoredChunk │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ filter: 筛选 top-k │
+ │ 输出: []scoredChunk │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ answer: 生成答案 │
+ │ 输出: Output │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 返回结果 │
+ │ {answer, sources} │
+ └──────────────────────┘
+```
+
+## 本章小结
+
+- **Graph Tool**:将复杂工作流封装为 Tool,支持多步骤协同
+- **compose.Workflow**:构建工作流的核心组件
+- **BatchNode**:并行处理多个任务
+- **FieldMapping**:跨节点传递数据
+- **可中断恢复**:Graph Tool 支持 Checkpoint 机制
+
+## 扩展思考
+
+**其他 Graph Tool 应用:**
+
+- 多文档 RAG:并行处理多个文档
+- 多模型协作:不同模型处理不同任务
+- 复杂决策树:根据条件选择不同分支
+
+**性能优化:**
+
+- 调整 MaxConcurrency 控制并发
+- 使用缓存避免重复计算
+- 流式输出提升用户体验
diff --git a/content/zh/docs/eino/quick_start/chapter_09_a2ui_protocol.md b/content/zh/docs/eino/quick_start/chapter_09_a2ui_protocol.md
new file mode 100644
index 0000000000..7fd499458d
--- /dev/null
+++ b/content/zh/docs/eino/quick_start/chapter_09_a2ui_protocol.md
@@ -0,0 +1,345 @@
+---
+Description: ""
+date: "2026-03-12"
+lastmod: ""
+tags: []
+title: 第九章:A2UI 协议(流式 UI 组件)
+weight: 9
+---
+
+本章目标:实现 A2UI 协议,将 Agent 的输出渲染为流式 UI 组件。
+
+## 重要说明:A2UI 的边界
+
+A2UI 并不属于 Eino 框架本身的范畴,它是一个业务层的 UI 协议/渲染方案。本章把 A2UI 集成进前面章节逐步构建出来的 Agent,是为了提供一个端到端、可落地的完整示例:从模型调用、工具调用、工作流编排,到最终把结果以更友好的 UI 方式呈现出来。
+
+在真实业务场景中,你完全可以根据产品形态选择不同的 UI 形式,例如:
+
+- Web / App:自定义组件、表格、卡片、图表等
+- IM/办公套件:消息卡片、交互式表单
+- 命令行:纯文本或 TUI(终端 UI)
+
+Eino 更关注"可组合的智能执行与编排能力",至于"如何呈现给用户",属于业务层可以自由扩展的一环。
+
+## 代码位置
+
+- 入口代码:[main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/main.go)
+- A2UI 实装:[a2ui/streamer.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/streamer.go)
+
+## 前置条件
+
+与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)
+
+## 运行
+
+在 `examples/quickstart/chatwitheino` 目录下执行:
+
+```bash
+go run .
+```
+
+输出示例:
+
+```
+starting server on http://localhost:8080
+```
+
+## 从文本到 UI:为什么需要 A2UI
+
+前八章我们实现的 Agent 只输出文本,但现代 AI 应用需要更丰富的交互。
+
+**纯文本输出的局限:**
+
+- 无法展示结构化数据(表格、列表、卡片等)
+- 无法实时更新(进度条、状态变化等)
+- 无法嵌入交互元素(按钮、表单、链接等)
+- 无法支持多媒体(图片、视频、音频等)
+
+**A2UI 的定位:**
+
+- **A2UI 是 Agent 到 UI 的协议**:定义了 Agent 输出如何映射到 UI 组件
+- **A2UI 支持流式渲染**:组件可以实时更新,无需等待完整响应
+- **A2UI 是声明式的**:Agent 只需声明"显示什么",UI 负责渲染
+
+**简单类比:**
+
+- **纯文本输出** = "终端命令行"(只能显示文本)
+- **A2UI** = "Web 应用"(可以显示任何 UI 组件)
+
+## 关键概念
+
+### A2UI 组件
+
+A2UI 定义了一系列 UI 组件类型:
+
+```go
+type ComponentType string
+
+const (
+ ComponentText ComponentType = "text" // 文本
+ ComponentMarkdown ComponentType = "markdown" // Markdown
+ ComponentCode ComponentType = "code" // 代码块
+ ComponentImage ComponentType = "image" // 图片
+ ComponentTable ComponentType = "table" // 表格
+ ComponentCard ComponentType = "card" // 卡片
+ ComponentButton ComponentType = "button" // 按钮
+ ComponentForm ComponentType = "form" // 表单
+ ComponentProgress ComponentType = "progress" // 进度条
+ ComponentDivider ComponentType = "divider" // 分隔线
+)
+```
+
+### A2UI 消息
+
+每条 A2UI 消息包含:
+
+```go
+type Message struct {
+ ID string // 消息 ID
+ Role string // user / assistant
+ Components []Component // UI 组件列表
+ Timestamp time.Time // 时间戳
+}
+```
+
+### A2UI 流式输出
+
+A2UI 支持流式输出组件:
+
+```go
+type StreamMessage struct {
+ Type string // add / update / delete
+ Index int // 组件索引
+ Component Component // 组件内容
+}
+```
+
+**流式更新类型:**
+
+- `add`:添加新组件
+- `update`:更新已有组件
+- `delete`:删除组件
+
+## A2UI 的实现
+
+### 1. 创建 A2UI Streamer
+
+```go
+streamer := a2ui.NewStreamer()
+```
+
+### 2. 添加组件
+
+```go
+// 添加文本组件
+streamer.AddText("正在处理您的请求...")
+
+// 添加进度条
+streamer.AddProgress(0, 100, "加载中")
+
+// 更新进度
+streamer.UpdateProgress(0, 50, "处理中")
+
+// 添加代码块
+streamer.AddCode("go", `fmt.Println("Hello, World!")`)
+
+// 添加表格
+streamer.AddTable([][]string{
+ {"Name", "Age", "City"},
+ {"Alice", "30", "New York"},
+ {"Bob", "25", "London"},
+})
+```
+
+### 3. 流式输出
+
+```go
+// 获取流式消息
+stream := streamer.Stream()
+
+for {
+ msg, ok := stream.Next()
+ if !ok {
+ break
+ }
+ // 发送到前端
+ sendToClient(msg)
+}
+```
+
+**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch09/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch09/main.go)):
+
+```go
+// 创建 A2UI Streamer
+streamer := a2ui.NewStreamer()
+
+// Agent 执行过程中添加组件
+streamer.AddText("我来帮你分析这个文件...")
+
+// 调用 Tool
+streamer.AddProgress(0, 0, "读取文件")
+result, err := tool.Run(ctx, args)
+streamer.UpdateProgress(0, 100, "完成")
+
+// 显示结果
+streamer.AddCode("json", result)
+
+// 流式输出
+stream := streamer.Stream()
+for {
+ msg, ok := stream.Next()
+ if !ok {
+ break
+ }
+ wsConn.WriteJSON(msg)
+}
+```
+
+## A2UI 与 Agent 的集成
+
+### 在 Agent 中使用 A2UI
+
+```go
+func buildAgent(ctx context.Context) (adk.Agent, error) {
+ return deep.New(ctx, &deep.Config{
+ Name: "A2UIAgent",
+ Description: "Agent with A2UI streaming output",
+ ChatModel: cm,
+ Backend: backend,
+ // 配置 A2UI Streamer
+ StreamingShell: backend,
+ })
+}
+```
+
+### 在 Runner 中使用 A2UI
+
+```go
+runner := adk.NewRunner(ctx, adk.RunnerConfig{
+ Agent: agent,
+ EnableStreaming: true,
+})
+
+// 执行 Agent
+events := runner.Run(ctx, history)
+
+// 将事件转换为 A2UI 组件
+streamer := a2ui.NewStreamer()
+for {
+ event, ok := events.Next()
+ if !ok {
+ break
+ }
+ if event.Output != nil && event.Output.MessageOutput != nil {
+ // 添加文本组件
+ streamer.AddText(event.Output.MessageOutput.Message.Content)
+ }
+}
+```
+
+## A2UI 流式渲染流程
+
+```
+┌─────────────────────────────────────────┐
+│ 用户:分析这个文件 │
+└─────────────────────────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Agent 开始处理 │
+ │ A2UI: AddText │
+ │ "正在分析..." │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 调用 Tool │
+ │ A2UI: AddProgress │
+ │ 进度: 0% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Tool 执行中 │
+ │ A2UI: UpdateProgress│
+ │ 进度: 50% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ Tool 完成 │
+ │ A2UI: UpdateProgress│
+ │ 进度: 100% │
+ └──────────────────────┘
+ ↓
+ ┌──────────────────────┐
+ │ 显示结果 │
+ │ A2UI: AddCode │
+ │ 代码块 │
+ └──────────────────────┘
+```
+
+## 前端集成
+
+### WebSocket 连接
+
+```javascript
+const ws = new WebSocket('ws://localhost:8080/ws');
+
+ws.onmessage = (event) => {
+ const msg = JSON.parse(event.data);
+ renderComponent(msg);
+};
+
+function renderComponent(msg) {
+ const { type, index, component } = msg;
+
+ switch (component.type) {
+ case 'text':
+ renderText(component.content);
+ break;
+ case 'code':
+ renderCode(component.language, component.content);
+ break;
+ case 'progress':
+ renderProgress(component.value, component.max, component.label);
+ break;
+ // ...
+ }
+}
+```
+
+## 本章小结
+
+- **A2UI**:Agent 到 UI 的协议,定义了 Agent 输出如何映射到 UI 组件
+- **组件类型**:文本、Markdown、代码、图片、表格、卡片、按钮、表单、进度条等
+- **流式输出**:支持实时添加、更新、删除组件
+- **声明式**:Agent 只需声明"显示什么",UI 负责渲染
+- **前端集成**:通过 WebSocket 实现实时通信
+
+## 系列收尾:这个 Quickstart Agent 的完整愿景
+
+到本章为止,我们用一个可以实际运行的 Agent 串起了 Eino 的核心能力。你可以把它理解为一个可扩展的"端到端 Agent 应用骨架":
+
+- 运行时:Runner 驱动执行,支持流式输出与事件模型
+- 工具层:Filesystem / Shell 等 Tool 能力接入,工具错误可被安全处理
+- 中间件:可插拔的 middleware/handler,用于错误处理、重试、审批等横切能力
+- 可观测:callbacks/trace 能力把关键链路打通,便于调试与线上观测
+- 人机协作:interrupt/resume + checkpoint 支持审批、补参、分支选择等交互式流程
+- 确定性编排:compose(graph/chain/workflow)把复杂业务流程组织为可维护、可复用的执行图
+- 业务交付:像 A2UI 这样的 UI 集成,属于业务层自由选择的一环,用来把 Agent 能力以合适的产品形态呈现给用户
+
+你可以在这个骨架上逐步替换/扩展任意环节:模型、工具、存储、工作流、前端渲染协议,而不需要推倒重来。
+
+## 扩展思考
+
+**其他组件类型:**
+
+- 图表组件(折线图、柱状图、饼图)
+- 地图组件
+- 时间线组件
+- 树形组件
+- 标签页组件
+
+**高级功能:**
+
+- 组件交互(点击、拖拽、输入)
+- 条件渲染
+- 组件动画
+- 响应式布局
diff --git a/content/zh/docs/eino/quick_start/simple_llm_application.md b/content/zh/docs/eino/quick_start/simple_llm_application.md
deleted file mode 100644
index ee737a0ccb..0000000000
--- a/content/zh/docs/eino/quick_start/simple_llm_application.md
+++ /dev/null
@@ -1,207 +0,0 @@
----
-Description: ""
-date: "2026-01-20"
-lastmod: ""
-tags: []
-title: 实现一个最简 LLM 应用
-weight: 1
----
-
-本指南将帮助你快速上手使用 Eino 框架中的 ChatModel 构建一个简单的 LLM 应用。我们将通过实现一个"程序员鼓励师"的例子,来展示如何使用 ChatModel。
-
-> 💡
-> 本文中示例的代码片段详见:[eino-examples/quickstart/chat](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chat)
-
-## **ChatModel 简介**
-
-ChatModel 是 Eino 框架中对对话大模型的抽象,它提供了统一的接口来与不同的大模型服务(如 OpenAI、Ollama 等)进行交互。
-
-> 组件更详细的介绍参考: [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide)
-
-## **Messages 的结构和使用**
-
-在 Eino 中,对话是通过 `schema.Message` 来表示的,这是 Eino 对一个对话消息的抽象定义。每个 Message 包含以下重要字段:
-
-- `Role`: 消息的角色,可以是:
- - `system`: 系统指令,用于设定模型的行为和角色
- - `user`: 用户的输入
- - `assistant`: 模型的回复
- - `tool`: 工具调用的结果
-- `Content`: 消息的具体内容
-
-## **实现程序员鼓励师**
-
-让我们通过实现一个程序员鼓励师来学习如何使用 ChatModel。这个助手不仅能提供技术建议,还能在程序员感到难过时给予心理支持。
-
-### 创建对话模板并生成消息
-
-Eino 提供了强大的模板化功能来构建要输入给大模型的消息:
-
-1. 模版渲染,支持三种模版格式:
-
- - FString:Python 风格的简单字符串格式化(例如:"你好,{name}!")
- - Jinja2:支持丰富表达式的 Jinja2 风格模板(例如:"你好,{{name}}!")
- - GoTemplate:Go 语言内置的 text/template 格式(例如:"你好,{{.name}}!")
-2. 消息占位符:支持插入一组消息(如对话历史)
-
-```go
-// optional=false 表示必需的消息列表,在模版输入中找不到对应变量会报错
-schema.MessagesPlaceholder("chat_history", false)
-```
-
-> 更详细的组件介绍可参考: [Eino: ChatTemplate 使用说明](/zh/docs/eino/core_modules/components/chat_template_guide)
-
-下面是完整的 FString 格式 + 消息占位符的对话模板创建及使用代码:
-
-```go
-// eino-examples/quickstart/chat/template.go
-
-import (
- "context"
-
- "github.com/cloudwego/eino/components/prompt"
- "github.com/cloudwego/eino/schema"
-)
-
-// 创建模板,使用 FString 格式
-template := prompt.FromMessages(schema.FString,
- // 系统消息模板
- schema.SystemMessage("你是一个{role}。你需要用{style}的语气回答问题。你的目标是帮助程序员保持积极乐观的心态,提供技术建议的同时也要关注他们的心理健康。"),
-
- // 插入需要的对话历史(新对话的话这里不填)
- schema.MessagesPlaceholder("chat_history", true),
-
- // 用户消息模板
- schema.UserMessage("问题: {question}"),
-)
-
-// 使用模板生成消息
-messages, err := template.Format(context.Background(), map[string]any{
- "role": "程序员鼓励师",
- "style": "积极、温暖且专业",
- "question": "我的代码一直报错,感觉好沮丧,该怎么办?",
- // 对话历史(这个例子里模拟两轮对话历史)
- "chat_history": []*schema.Message{
- schema.UserMessage("你好"),
- schema.AssistantMessage("嘿!我是你的程序员鼓励师!记住,每个优秀的程序员都是从 Debug 中成长起来的。有什么我可以帮你的吗?", nil),
- schema.UserMessage("我觉得自己写的代码太烂了"),
- schema.AssistantMessage("每个程序员都经历过这个阶段!重要的是你在不断学习和进步。让我们一起看看代码,我相信通过重构和优化,它会变得更好。记住,Rome wasn't built in a day,代码质量是通过持续改进来提升的。", nil),
- },
-})
-```
-
-### 创建 ChatModel
-
-ChatModel 是 Eino 框架中最核心的组件之一,它提供了与各种大语言模型交互的统一接口。Eino 目前支持以下大语言模型的实现:
-
-- OpenAI:支持 GPT-3.5/GPT-4 等模型 (同样支持 azure 提供的 openai 服务)
-- Ollama:支持本地部署的开源模型
-- Ark:火山引擎上的模型服务 (例如字节的豆包大模型)
-- 更多模型正在支持中
-
-> 支持的模型可以参考:[Eino: 生态集成](/zh/docs/eino/ecosystem_integration)
-
-下面我们以 OpenAI 和 Ollama 为例,展示如何创建和使用 ChatModel:
-
-#### **OpenAI (和下方 ollama 2 选 1)**
-
-```go
-// eino-examples/quickstart/chat/openai.go
-
-import (
- "os"
-
- "github.com/cloudwego/eino-ext/components/model/openai"
-)
-
-chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
- Model: "gpt-4o", // 使用的模型版本
- APIKey: os.Getenv("OPENAI_API_KEY"), // OpenAI API 密钥
-})
-```
-
-> OpenAI ChatModel 的详细信息可以参考:[ChatModel - OpenAI](https://github.com/cloudwego/eino-ext/blob/main/components/model/openai/README_zh.md)
-
-#### **Ollama(和上方 openai 2 选 1)**
-
-Ollama 支持在本地运行开源模型,适合对数据隐私有要求或需要离线使用的场景。
-
-```go
-// eino-examples/quickstart/chat/ollama.go
-
-import (
- "github.com/cloudwego/eino-ext/components/model/ollama"
-)
-
-
-chatModel, err := ollama.NewChatModel(ctx, &ollama.ChatModelConfig{
- BaseURL: "http://localhost:11434", // Ollama 服务地址
- Model: "llama2", // 模型名称
-})
-```
-
-> Ollama ChatModel 的详细信息可以参考:[ChatModel - Ollama](https://github.com/cloudwego/eino-ext/blob/main/components/model/ollama/README_zh.md)
-
-Eino 为大模型提供了统一的 ChatModel 抽象,并提供了开箱即用的各类 LLM 实现,因此业务代码可以无需关注模型实现细节从而专注业务逻辑编写,模型实现迭代时不会影响核心业务逻辑,这意味着开发者可以轻松地在不同的模型之间切换,而无需修改大量代码。
-
-### 运行 ChatModel
-
-经过前两步得到 ChatModel 的输入 messages 和初始化完成后的 ChatModel 实例后,可以开始尝试运行 ChatModel 了。Eino ChatModel 提供了两种运行模式:输出完整消息(generate)和输出消息流(stream):
-
-```go
-// eino-examples/quickstart/chat/generate.go
-
-/*** create messages
-* messages, err := xxx
-*/
-
-/*** create chat model
-* chatModel, err := xxx
-*/
-
-result, err := chatModel.Generate(ctx, messages)
-streamResult, err := chatModel.Stream(ctx, messages)
-```
-
-在实际应用中,有很多场景需要使用流式响应,主要的场景例如「提升用户体验」:stream 运行模式让 ChatModel 提供类似打字机的输出效果,使用户更早得到模型响应。
-
-Eino 中对流式输出的处理方式如下:
-
-```go
-// eino-examples/quickstart/chat/stream.go
-
-import (
- "io"
- "log"
-
- "github.com/cloudwego/eino/schema"
-)
-
-func reportStream(sr *schema.StreamReader[*schema.Message]) {
- defer sr.Close()
-
- i := 0
- for {
- message, err := sr.Recv()
- if err == io.EOF { // 流式输出结束
- return
- }
- if err != nil {
- log.Fatalf("recv failed: %v", err)
- }
- log.Printf("message[%d]: %+v\n", i, message)
- i++
- }
-}
-```
-
-完整实现参见:[eino-examples/quickstart/chat/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chat/main.go)
-
-## **总结**
-
-本示例通过一个程序员鼓励师的案例,展示了如何使用 Eino 框架构建 LLM 应用。从 ChatModel 的创建到消息模板的使用,再到实际的对话实现,相信你已经对 Eino 框架有了基本的了解。无论是选择 OpenAI、Ollama 还是其他模型实现,Eino 都提供了统一且简单的使用方式。希望这个示例能帮助你快速开始构建自己的 LLM 应用。
-
-## **关联阅读**
-
-- 快速开始
- - [Agent-让大模型拥有双手](/zh/docs/eino/quick_start/agent_llm_with_tools)
diff --git a/content/zh/docs/eino/release_notes_and_migration/v02_second_release.md b/content/zh/docs/eino/release_notes_and_migration/v02_second_release.md
index 75cf940695..fbda1b7ed1 100644
--- a/content/zh/docs/eino/release_notes_and_migration/v02_second_release.md
+++ b/content/zh/docs/eino/release_notes_and_migration/v02_second_release.md
@@ -74,7 +74,7 @@ weight: 2
### BugFix
-- Fixed the SSTI vulnerability in the Jinja chat template(langchaingo gonja 模板注入)
+- Fixed the SSTI vulnerability in the Jinja chat template(langchaingo 存在 gonja 模板注入)
## v0.2.0