@@ -78,57 +78,57 @@ This document serves as an example index for the eino-examples project, helping
## 🔗 Compose (Orchestration)
-### Chain
+### Chain (Chain Orchestration)
-### Workflow
+### Workflow (Workflow Orchestration)
-### Batch
+### Batch (Batch Processing)
### Multi-Agent
@@ -143,8 +143,8 @@ This document serves as an example index for the eino-examples project, helping
| Directory | Name | Description |
-| components/tool/jsonschema | JSON Schema Tool | Demonstrates how to define tool parameters using JSON Schema |
+| components/tool/jsonschema | JSON Schema Tool | Demonstrates how to use JSON Schema to define tool parameters |
| components/tool/mcptool/callresulthandler | MCP Tool Result Handler | Demonstrates custom handling of MCP tool call results |
| components/tool/middlewares/errorremover | Error Remover Middleware | Tool call error handling middleware, converting errors to friendly prompts |
| components/tool/middlewares/jsonfix | JSON Fix Middleware | Fixing malformed JSON parameters generated by LLM |
@@ -182,7 +182,7 @@ This document serves as an example index for the eino-examples project, helping
@@ -206,7 +206,7 @@ This document serves as an example index for the eino-examples project, helping
| Directory | Name | Description |
-| quickstart/chat | Chat QuickStart | Most basic LLM conversation example, including templates, generation, streaming output |
+| quickstart/chat | Chat QuickStart | The most basic LLM conversation example, including template, generation, streaming output |
| quickstart/eino_assistant | Eino Assistant | Complete RAG application example, including knowledge indexing, Agent service, Web interface |
| quickstart/todoagent | Todo Agent | Simple Todo management Agent example |
@@ -217,7 +217,7 @@ This document serves as an example index for the eino-examples project, helping
| Directory | Name | Description |
-| devops/debug | Debug Tools | Demonstrates how to use Eino's debugging features, supporting Chain and Graph debugging |
+| devops/debug | Debug Tools | Demonstrates how to use Eino debugging features, supporting Chain and Graph debugging |
| devops/visualize | Visualization Tools | Rendering Graph/Chain/Workflow as Mermaid diagrams |
diff --git a/content/en/docs/eino/FAQ.md b/content/en/docs/eino/FAQ.md
index 4467a8bc681..ebe0ab2b237 100644
--- a/content/en/docs/eino/FAQ.md
+++ b/content/en/docs/eino/FAQ.md
@@ -1,23 +1,23 @@
---
Description: ""
-date: "2026-01-30"
+date: "2026-03-02"
lastmod: ""
tags: []
title: FAQ
-weight: 7
+weight: 11
---
-# Q: cannot use openapi3.TypeObject (untyped string constant "object") as *openapi3.Types value in struct literal,cannot use types (variable of type string) as *openapi3.Types value in struct literal
+# Q: cannot use openapi3.TypeObject (untyped string constant "object") as *openapi3.Types value in struct literal, cannot use types (variable of type string) as *openapi3.Types value in struct literal
-Ensure the `github.com/getkin/kin-openapi` dependency version does not exceed `v0.118.0`. Starting from Eino `v0.6.0`, Eino no longer depends on the `kin-openapi` library.
+Check that the github.com/getkin/kin-openapi dependency version does not exceed v0.118.0. Versions after Eino V0.6.0 no longer depend on the kin-openapi library.
-# Q: During agent streaming, it never reaches ToolsNode, or streaming is lost and appears non-streaming.
+# Q: Agent streaming calls do not enter the ToolsNode node. Or streaming effect is lost, behaving as non-streaming.
-- First, update Eino to the latest version.
+- First update Eino to the latest version
-Different models output tool calls differently in streaming mode. Some models (e.g., OpenAI) emit tool calls directly; others (e.g., Claude) might emit text first and then the tool call. You therefore need different logic to determine whether a tool call is present in a streamed message.
+Different models may output tool calls differently in streaming mode: some models (like OpenAI) output tool calls directly; some models (like Claude) output text first, then output tool calls. Therefore, different methods need to be used for judgment. This field is used to specify the function that determines whether the model's streaming output contains tool calls.
-The ReAct Agent `Config` has a `StreamToolCallChecker`. If omitted, the agent uses a default checker that determines a tool call by inspecting whether any non-empty early chunk contains tool calls:
+The Config of ReAct Agent has a StreamToolCallChecker field. If not filled, the Agent will use "non-empty packet" to determine whether it contains tool calls:
```go
func firstChunkStreamToolCallChecker(_ context.Context, sr *schema.StreamReader[*schema.Message]) (bool, error) {
@@ -45,9 +45,9 @@ func firstChunkStreamToolCallChecker(_ context.Context, sr *schema.StreamReader[
}
```
-This default works when the Tool Call message contains only a tool call.
+The above default implementation is suitable for: Tool Call Messages output by the model only contain Tool Calls.
-It does not fit cases where a non-empty content chunk appears before the tool call. In such cases, provide a custom checker like this:
+Cases where the default implementation is not applicable: there are non-empty content chunks before outputting Tool Calls. In this case, a custom tool call checker is needed:
```go
toolCallChecker := func(ctx context.Context, sr *schema.StreamReader[*schema.Message]) (bool, error) {
@@ -71,108 +71,139 @@ toolCallChecker := func(ctx context.Context, sr *schema.StreamReader[*schema.Mes
}
```
-Note: this custom `StreamToolCallChecker` checks all chunks for tool calls. When the model is outputting a normal answer, this may reduce "early streaming detection", because it waits until all chunks are inspected. To preserve streaming responsiveness, try guiding the model with prompts:
+The above custom StreamToolCallChecker needs to check **all packets** for ToolCall when the model normally outputs an answer, which causes the "streaming judgment" effect to be lost. To preserve the "streaming judgment" effect as much as possible, the suggestion is:
> 💡
-> Add prompt constraints such as: "If a tool is required, output only the tool call; do not output text."
+> Try adding prompts to constrain the model not to output extra text when calling tools, for example: "If you need to call a tool, output the tool directly without outputting text."
>
-> Models vary in how much they adhere to such prompts. Tune and validate for your chosen model.
+> Different models may be affected differently by prompts. In actual use, you need to adjust the prompts yourself and verify the effect.
# Q: [github.com/bytedance/sonic/loader](http://github.com/bytedance/sonic/loader): invalid reference to runtime.lastmoduledatap
-Older versions of `sonic` are incompatible with `go1.24`. Upgrade to `v1.13.2` or higher.
+Old versions of sonic are incompatible with go1.24. Update to version greater than v1.13.2.
-# Q: Tool input deserialization failed: `failed to invoke tool call {tool_call_id}: unmarshal input fail`
+# Q: Tool Input deserialization failed: failed to invoke tool call {tool_call_id}: unmarshal input fail
-Models typically do not produce invalid JSON. Investigate the specific reason for deserialization failure; in most cases this is due to output truncation when the model's response exceeds limits.
+Currently, models generally do not produce illegal JSON output. First confirm what the deserialization failure reason is. It's most likely caused by model output being truncated due to excessive length.
-# Q: How can I implement batch processing nodes in Eino (like Coze's batch nodes)?
+# Q: How does Eino implement batch processing nodes? Similar to batch processing nodes in Coze
-Eino currently does not support batch processing. Two options:
+Eino currently does not support batch processing. There are two optional methods:
-1. Dynamically build the graph per request — the overhead is low. Note that `Chain Parallel` requires the number of parallel nodes to be greater than one.
-2. Implement a custom batch node and handle batching inside the node.
+1. Dynamically build the graph on demand for each request, with low additional cost. Note that Chain Parallel requires more than one parallel node.
+2. Custom batch processing node, where the node handles batch processing tasks internally
Code example: [https://github.com/cloudwego/eino-examples/tree/main/compose/batch](https://github.com/cloudwego/eino-examples/tree/main/compose/batch)
-# Q: Does Eino support structured model outputs?
+# Q: Does Eino support structured model output?
-Yes, in two steps:
+Two steps. First, require the model to output structured data, with three methods:
-1. Ensure the model outputs structured data. Options:
- - Some models support configuration for structured output (e.g., OpenAI response format).
- - Use tool calls to obtain structured results.
- - Prompt the model explicitly to output structured data.
-2. After obtaining a structured message, use `schema.NewMessageJSONParser` to parse the message into your target struct.
+1. Some models support direct configuration (like OpenAI's response format). Check if there's such configuration in the model settings.
+2. Obtain through tool call functionality
+3. Write prompts requiring the model to output structured data
-# Q: How to access Reasoning Content / "thinking" output from a chat model?
+After getting structured output from the model, you can use schema.NewMessageJSONParser to convert the message to the struct you need.
-If the model implementation supports Reasoning Content, it is stored in the `ReasoningContent` field of the output `Message`.
+# Q: How to get the Reasoning Content/reasoning/deep thinking content output by the model (chat model):
-# Q: Errors include `context deadline exceeded`, `timeout`, or `context canceled`
+If the model wrapper supports outputting Reasoning Content/reasoning/deep thinking content, this content will be stored in the ReasoningContent field of the Message output by the model.
-Cases:
+# Q: Error contains "context deadline exceeded" "timeout" "context canceled"
-1. `context.canceled`: While executing a graph or agent, the user code passed a cancelable context and triggered cancellation. Investigate your application's context-cancel logic. This is unrelated to the Eino framework.
-2. `context deadline exceeded`: Two common possibilities:
- 1. During graph or agent execution, the user code passed a context with a timeout, which was reached.
- 2. A `ChatModel` or other external resource has its own timeout configured (or its HTTP client does), which was reached.
+Discussion by case:
-Inspect the thrown error for `node path: [node name x]`. If the node name is not a `ChatModel` or any node that performs external calls, it is likely case 2-a; otherwise, it is likely case 2-b.
+1. context.canceled: When executing a graph or agent, the user passed in a cancelable context and initiated a cancellation. Check the context cancel operation in the application layer code. This error is unrelated to the Eino framework.
+2. Context deadline exceeded: Could be two situations:
+ 1. When executing a graph or agent, the user passed in a context with timeout, triggering a timeout.
+ 2. Timeout or httpclient with timeout was configured for ChatModel or other external resources, triggering a timeout.
-If you suspect 2-a, trace upstream to find where a timeout was set on the context (common sources include FaaS platforms, gateways, etc.).
+Check `node path: [node name x]` in the thrown error. If the node name is not a node with external calls like ChatModel, it's most likely situation 2-a; otherwise, it's most likely situation 2-b.
-If you suspect 2-b, check whether the node itself configures a timeout (e.g., Ark ChatModel `Timeout`, or OpenAI ChatModel via an `HttpClient` with `Timeout`). If none are configured but timeouts still occur, it may be the SDK's default timeout. Known defaults: Ark SDK 10 minutes; Deepseek SDK 5 minutes.
+If you suspect it's situation 2-a, check which link in the upstream chain set the timeout on context. Common possibilities include FaaS platforms, etc.
-# Q: How to access parent graph `State` within a subgraph?
+If you suspect it's situation 2-b, check whether the node has its own timeout configuration, such as Ark ChatModel configured with Timeout, or OpenAI ChatModel configured with HttpClient (with internal Timeout configuration). If neither is configured but still timing out, it may be the model SDK's default timeout. Known default timeouts: Ark SDK 10 minutes, Deepseek SDK 5 minutes.
-If the subgraph and parent graph have different `State` types, use `ProcessState[ParentStateType]()` to process the parent's state. If they share the same `State` type, make the types distinct (for example, with a type alias: `type NewParentStateType StateType`).
+# Q: How to get the parent graph's State in a subgraph
-# Q: How does `eino-ext` adapt multimodal input/output for supported models?
+If the subgraph and parent graph have different State types, you can use `ProcessState[parent graph state type]()` to process the parent graph's State. If the subgraph and parent graph have the same State type, make the State types different, for example, using a type alias: `type NewParentStateType StateType`.
-For multimodal support, see [https://www.cloudwego.io/docs/eino/ecosystem_integration/chat_model](https://www.cloudwego.io/docs/eino/ecosystem_integration/chat_model) and the corresponding examples for each model.
+# Q: How to adapt multimodal input/output for Models supported by eino-ext?
-# Q: Using `UserInputMultiContent` for multimodal input, but the model side seems to miss the data or cannot read `multicontent`
+For multimodal input/output scenarios supported by eino-ext, refer to the Examples in [https://www.cloudwego.io/docs/eino/ecosystem_integration/chat_model](https://www.cloudwego.io/docs/eino/ecosystem_integration/chat_model) for the corresponding model.
-Recent versions of Eino introduce `UserInputMultiContent` and `AssistantGenMultiContent` for multimodal user input and model output respectively. All `eino-ext` chat model implementations have been adapted. If the model does not receive the multimodal payload, upgrade the provider package to the latest version and try again.
+# Q: Using the latest multimodal support field UserInputMultiContent to input multimodal data, but the model side doesn't seem to have my multimodal data, or can't read multicontent content during multimodal input
-# Q: After upgrading to `0.6.x`, there are breaking changes
+The latest version of Eino introduces UserInputMultiContent and AssistantGenMultiContent to represent user-side input multimodal data and model-side returned multimodal data respectively. The chatmodel implementations in eino-ext have all been adapted. If you find that the model side has not received multimodal information, try upgrading the model package you're using. Use go get to update to the latest version and try running again to see if the problem is resolved.
-Per the migration plan [Migration from OpenAPI 3.0 Schema Object to JSONSchema in Eino · Discussion #397](https://github.com/cloudwego/eino/discussions/397), Eino `v0.6.1` removed the dependency on `getkin/kin-openapi` and all OpenAPI 3.0-related code.
+# Q: After upgrading to version 0.6.x, there are incompatibility issues
-If `eino-ext` modules error with `undefined: schema.NewParamsOneOfByOpenAPIV3`, upgrade those modules to the latest versions.
+According to the previous community announcement plan [Migration from OpenAPI 3.0 Schema Object to JSONSchema in Eino · cloudwego/eino · Discussion #397](https://github.com/cloudwego/eino/discussions/397), Eino V0.6.1 has been released. Important update content includes removing the getkin/kin-openapi dependency and all OpenAPI 3.0 related code.
-If schema migration is complex, use the helper tooling in the [JSONSchema conversion guide](https://bytedance.larkoffice.com/wiki/ZMaawoQC4iIjNykzahwc6YOknXf).
+For errors like undefined: schema.NewParamsOneOfByOpenAPIV3 in some eino-ext modules, upgrade the error-reporting eino-ext module to the latest version.
-# Q: Which ChatModels in `eino-ext` support the Responses API form?
+If schema transformation is complex, you can use the tool methods in the [JSONSchema Conversion Methods](https://bytedance.larkoffice.com/wiki/ZMaawoQC4iIjNykzahwc6YOknXf) document to assist with conversion.
-- In `eino-ext`, currently only the ARK Chat Model can create a ResponsesAPI ChatModel via **NewResponsesAPIChatModel**. Other models do not support creating or using ResponsesAPI.
-- In `eino-byted-ext`, only bytedgpt supports creating Response API via **NewResponsesAPIChatModel**. Other chatmodels have not implemented Response API Client.
- - Version `components/model/gemini/v0.1.16` already supports `thought_signature` passback. Check if your gemini version meets the requirement. If using bytedgemini (code.byted.org/flow/eino-byted-ext/components/model/bytedgemini) chatmodel implementation, check if its dependency `components/model/gemini` is the latest version, or directly upgrade gemini via go get.
- - Replace the currently used bytedgpt package with [code.byted.org/flow/eino-byted-ext/components/model/bytedgemini](http://code.byted.org/flow/eino-byted-ext/components/model/bytedgemini) implementation, upgrade to the latest version, and check the example code to confirm how to pass BaseURL.
- - If you encounter this error, please confirm whether the base url filled in when generating chat model is the chat completion URL or the ResponseAPI URL. In most cases, it's an incorrect ResponseAPI Base URL being passed.
+# Q: Which models provided by Eino-ext ChatModel support Response API format calls?
-# Q: How to troubleshoot ChatModel call errors? For example: [NodeRunError] failed to create chat completion: error, status code: 400, status: 400 Bad Request.
+- Currently in Eino-Ext, only ARK's Chat Model can create ResponsesAPI ChatModel through **NewResponsesAPIChatModel**. Other models currently do not support ResponsesAPI creation and usage.
+- In Eino-byted-ext, only bytedgpt supports creating Response API through **NewResponsesAPIChatModel**. Other chatmodels have not implemented Response API Client.
+ - Version components/model/gemini/v0.1.16 already supports thought_signature passback. Check if the gemini version meets requirements. If using bytedgemini (code.byted.org/flow/eino-byted-ext/components/model/bytedgemini) chatmodel implementation, check if its dependent components/model/gemini is the latest version, or directly use go get to upgrade gemini.
+ - Replace the currently used bytedgpt package with the implementation from [code.byted.org/flow/eino-byted-ext/components/model/bytedgemini](http://code.byted.org/flow/eino-byted-ext/components/model/bytedgemini) and upgrade to the latest version. Refer to example code to confirm how to pass BaseURL.
+ - If you encounter this error, confirm whether the base url filled in when generating chat model is the chat completion URL or the ResponseAPI URL. In most cases, the Response API Base URL was incorrectly passed.
-This type of error comes from the model API (such as GPT, Ark, Gemini, etc.). The general approach is to check whether the actual HTTP Request to the model API has missing fields, incorrect field values, wrong BaseURL, etc. It's recommended to print the actual HTTP Request via logs and verify/modify the HTTP Request through direct HTTP requests (such as sending Curl from command line or using Postman). After identifying the problem, modify the corresponding Eino code accordingly.
+# Q: How to troubleshoot ChatModel call errors? For example, [NodeRunError] failed to create chat completion: error, status code: 400, status: 400 Bad Request.
-For how to print the actual HTTP Request to the model API via logs, refer to this code example: [https://github.com/cloudwego/eino-examples/tree/main/components/model/httptransport](https://github.com/cloudwego/eino-examples/tree/main/components/model/httptransport)
+This type of error is an error from the model API (such as GPT, Ark, Gemini, etc.). The general approach is to check whether the actual HTTP Request calling the model API has missing fields, incorrect field values, wrong BaseURL, etc. It's recommended to print out the actual HTTP Request through logs and verify/modify the HTTP Request through direct HTTP request methods (such as sending Curl from command line or using Postman for direct requests). After locating the problem, modify the corresponding issues in the Eino code accordingly.
-# Q: The gemini chat model created under eino-ext repository doesn't support passing multimodal via Image URL? How to adapt?
+For how to print out the actual HTTP Request of the model API through logs, refer to this code example: [https://github.com/cloudwego/eino-examples/tree/main/components/model/httptransport](https://github.com/cloudwego/eino-examples/tree/main/components/model/httptransport)
-Currently, the gemini Chat model under the Eino-ext repository has added support for passing URL types. Use `go get github.com/cloudwego/eino-ext/components/model/gemini` to update to [components/model/gemini/v0.1.22](https://github.com/cloudwego/eino-ext/releases/tag/components%2Fmodel%2Fgemini%2Fv0.1.22), the latest version, and test whether passing Image URL meets your business requirements.
+# Q: The gemini chat model created under the eino-ext repository doesn't support using Image URL to pass multimodal data? How to adapt?
-# Q: Before calling tools (including MCP tools), JSON Unmarshal failure error occurs. How to solve it?
+Currently, the gemini Chat model under the Eino-ext repository has already added support for passing URL types. Use go get github.com/cloudwego/eino-ext/components/model/gemini to update to [components/model/gemini/v0.1.22](https://github.com/cloudwego/eino-ext/releases/tag/components%2Fmodel%2Fgemini%2Fv0.1.22), the current latest version. Test passing Image URL to see if it meets business requirements.
-The Argument field in the Tool Call generated by ChatModel is a string. When the Eino framework calls the tool based on this Argument string, it first performs JSON Unmarshal. At this point, if the Argument string is not valid JSON, JSON Unmarshal will fail with an error like: `failed to call mcp tool: failed to marshal request: json: error calling MarshalJSON for type json.RawMessage: unexpected end of JSON input`
+# Q: Before calling tools (including MCP tool), getting JSON Unmarshal failure error, how to solve
-The fundamental solution to this problem is to rely on the model to output valid Tool Call Arguments. From an engineering perspective, we can try to fix some common JSON format issues, such as extra prefixes/suffixes, special character escaping issues, missing braces, etc., but cannot guarantee 100% correction. A similar fix implementation can be found in the code example: [https://github.com/cloudwego/eino-examples/tree/main/components/tool/middlewares/jsonfix](https://github.com/cloudwego/eino-examples/tree/main/components/tool/middlewares/jsonfix)
+The Argument field in Tool Call generated by ChatModel is a string. When the Eino framework calls tools based on this Argument string, it first does JSON Unmarshal. At this point, if the Argument string is not valid JSON, JSON Unmarshal will fail, throwing an error like: `failed to call mcp tool: failed to marshal request: json: error calling MarshalJSON for type json.RawMessage: unexpected end of JSON input`
+
+The fundamental solution to this problem is to rely on the model to output valid Tool Call Arguments. Engineering-wise, we can try to fix some common JSON format issues, such as extra prefixes/suffixes, special character escaping issues, missing braces, etc., but cannot guarantee 100% correction. A similar fix implementation can be referenced in this code example: [https://github.com/cloudwego/eino-examples/tree/main/components/tool/middlewares/jsonfix](https://github.com/cloudwego/eino-examples/tree/main/components/tool/middlewares/jsonfix)
# Q: How to visualize the topology structure of a graph/chain/workflow?
-Use the `GraphCompileCallback` mechanism to export the topology structure during `graph.Compile`. A code example for exporting to mermaid diagram: [https://github.com/cloudwego/eino-examples/tree/main/devops/visualize](https://github.com/cloudwego/eino-examples/tree/main/devops/visualize)
+Use the `GraphCompileCallback` mechanism to export the topology structure during `graph.Compile`. A code example for exporting as a mermaid diagram: [https://github.com/cloudwego/eino-examples/tree/main/devops/visualize](https://github.com/cloudwego/eino-examples/tree/main/devops/visualize)
+
+- For obtaining intermediate structures in Flow/React Agent scenarios, refer to the document [Eino: ReAct Agent Manual](/docs/eino/core_modules/flow_integration_components/react_agent_manual)
+
+# Q: Gemini model error missing a `thought_signature`
-## Q: In Eino, when using Flow/React Agent scenarios, how to get the Tool Call Message and the Tool Result of the tool invocation?
+Gemini model protocol is not openai-compatible. Use the gemini wrapper [https://github.com/cloudwego/eino-ext/tree/main/components/model/gemini](https://github.com/cloudwego/eino-ext/tree/main/components/model/gemini). If using ModelHub platform models, use the internal gemini wrapper [https://code.byted.org/flow/eino-byted-ext/tree/master/components/model/bytedgemini](https://code.byted.org/flow/eino-byted-ext/tree/master/components/model/bytedgemini). Initialization reference code:
-- For getting intermediate results in Flow/React Agent scenarios, refer to the document [Eino: ReAct Agent User Manual](/docs/eino/core_modules/flow_integration_components/react_agent_manual)
- - Additionally, you can replace Flow/React Agent with ADK's ChatModel Agent. For details, refer to [Eino ADK: Overview](/docs/eino/core_modules/eino_adk/agent_preview)
+```
+cm, err := bytedgemini.NewChatModel(ctx, &bytedgemini.Config{
+ ClientConfig: genai.ClientConfig{
+ APIKey: apiKey,
+ Backend: genai.BackendGeminiAPI,
+ // uncomment if you want to print the actual request in CURL format
+ // HTTPClient: &http.Client{Transport: NewCurlLogger(http.DefaultTransport, log.Printf)},
+ HTTPOptions: genai.HTTPOptions{
+ // this is base URL for cn, other regions:
+ // - sg: gpt-i18n.byteintl.net
+ // - sg from office network: genai-sg-og.tiktok-row.org
+ // - va: search-va.byteintl.net
+ // - va from office network: genai-va-og.tiktok-row.org
+ // - Non-TT: gpt-i18n.bd.byteintl.net
+ // - US-TTP: gpt.tiktokd.net
+ // - EU-TTP, GCP: gpt.tiktoke.org
+ // - JP: gpt-jp.byteintl.net
+ // see also: https://bytedance.larkoffice.com/wiki/wikcnUPXCY2idGyg2AXKPvay4pd
+ BaseURL: "https://search.bytedance.net/gpt/openapi/online/multimodal/crawl/google/",
+ APIVersion: "v1",
+ },
+ },
+ Model: modelName,
+ ThinkingConfig: &genai.ThinkingConfig{
+ IncludeThoughts: true,
+ ThinkingBudget: nil,
+ },
+})
+```
diff --git a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md
index 3b82e295512..7ee9204b9ae 100644
--- a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md
+++ b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual.md
@@ -1,29 +1,29 @@
---
Description: ""
-date: "2026-01-30"
+date: "2026-03-02"
lastmod: ""
tags: []
-title: 'Eino: Callback Manual'
+title: 'Eino: Callback User Manual'
weight: 5
---
-## Problem Statement
+## Problem Solved
-Components (including Lambdas) and Graph orchestration together solve the problem of "defining business logic". Cross-cutting concerns like logging, tracing, metrics, and UI surfacing need a mechanism to inject functionality into Components (including Lambdas) and Graphs.
+Components (including Lambda) and Graph orchestration together solve the problem of "defining business logic". Cross-cutting concerns such as logging, tracing, metrics, and display on screen require mechanisms to inject functionality into Components (including Lambda) and Graphs.
-On the other hand, users may want to access intermediate information during the execution of a specific Component implementation, such as the DB Name queried by VikingDBRetriever, or parameters like temperature requested by ArkChatModel. A mechanism is needed to expose intermediate state.
+On the other hand, users may want to obtain intermediate information during the execution of a specific Component implementation, such as VikingDBRetriever providing additional DB Name for queries, or ArkChatModel providing additional parameters like temperature for requests. A mechanism is needed to expose intermediate states.
-Callbacks support both "**cross-cutting concern injection**" and "**intermediate state exposure**". Specifically: users provide and register "functions" (Callback Handlers), and Components and Graphs call back these functions at fixed "timings" (or aspects/points), providing corresponding information.
+Callbacks support "**cross-cutting functionality injection**" and "**intermediate state exposure**". Specifically: users provide and register "functions" (Callback Handlers), and Components and Graphs call back these functions at fixed "timings" (or aspects, positions), providing corresponding information.
## Core Concepts
-The core concepts connected together: **Entities** in Eino such as Components and Graphs, at fixed **timings** (Callback Timing), call back user-provided **functions** (Callback Handlers), passing **who they are** (RunInfo) and **what happened at that moment** (Callback Input & Output).
+The core concepts connect as follows: **Entities** in Eino such as Components and Graphs, at fixed **timings** (Callback Timing), call back **functions** (Callback Handlers) provided by users, passing out **who they are** (RunInfo) and **what happened at that time** (Callback Input & Output).
### Triggering Entities
-Components (including officially defined component types and Lambdas), Graph Nodes (as well as Chain/Workflow Nodes), and Graphs themselves (as well as Chain/Workflow). All three types of entities have needs for cross-cutting concern injection and intermediate state exposure, so they all trigger callbacks. See the "[Triggering Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section below for details.
+Components (including officially defined component types and Lambda), Graph Nodes (as well as Chain/Workflow Nodes), and Graphs themselves (as well as Chain/Workflow). These three types of entities all have needs for cross-cutting functionality injection and intermediate state exposure, so they all trigger callbacks. See the "[Triggering Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section below for details.
-### Triggering Timings
+### Triggering Timing
```go
// CallbackTiming enumerates all the timing of callback aspects.
@@ -38,7 +38,7 @@ const (
)
```
-Different triggering entities, in different scenarios, whether to trigger OnStart or OnStartWithStreamInput (same for OnEnd/OnEndWithStreamOutput), the specific rules are detailed in the "[Triggering Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section below.
+Different triggering entities, in different scenarios, trigger OnStart or OnStartWithStreamInput (same logic for OnEnd/OnEndWithStreamOutput). The specific rules are detailed in the "[Triggering Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section below.
### Callback Handler
@@ -56,19 +56,19 @@ type Handler interface {
A Handler is a struct that implements the above 5 methods (corresponding to 5 triggering timings). Each method receives three pieces of information:
-- Context: Used to **receive custom information that may have been set by preceding timings of the same Handler**.
+- Context: Used to **receive custom information** that may be set by preceding triggering timings of the same Handler.
- RunInfo: Metadata of the entity triggering the callback.
- Input/Output/InputStream/OutputStream: Business information at the time of callback triggering.
-And all return a new Context: used to **pass information between different triggering timings of the same Handler**.
+And all return a new Context: for **passing information between different triggering timings of the same Handler**.
-If a Handler doesn't want to focus on all 5 triggering timings, but only some of them, such as only OnStart, it's recommended to use `NewHandlerBuilder().OnStartFn(...).Build()`. If you don't want to focus on all component types, but only specific components like ChatModel, it's recommended to use `NewHandlerHelper().ChatModel(...).Handler()`, which only receives ChatModel callbacks and gets typed CallbackInput/CallbackOutput. See the "[Handler Implementation Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section for details.
+If a Handler doesn't want to focus on all 5 triggering timings, but only some of them, such as only OnStart, it's recommended to use `NewHandlerBuilder().OnStartFn(...).Build()`. If you don't want to focus on all component types, but only specific components, such as ChatModel, it's recommended to use `NewHandlerHelper().ChatModel(...).Handler()`, which allows you to receive only ChatModel callbacks and get a specifically typed CallbackInput/CallbackOutput. See the "[Handler Implementation Methods](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual)" section for details.
There is **no** guaranteed triggering order between different Handlers.
### RunInfo
-Describes the metadata of the entity triggering the Callback.
+Describes the metadata of the entity itself that triggers the Callback.
```go
// RunInfo contains information about the running component that triggers callbacks.
@@ -79,33 +79,33 @@ type RunInfo struct {
}
```
-- Name: A name with business meaning, needs to be specified by the user, empty string if not specified. For different triggering entities:
- - Component: When in a Graph, uses Node Name. When used standalone outside a Graph, manually set by user. See "Injecting RunInfo" and "Using Components Standalone"
- - Graph Node: Uses Node Name `func WithNodeName(n string) GraphAddNodeOpt`
+- Name: A name with business meaning, specified by the user. If not specified, it's an empty string. For different triggering entities:
+ - Component: When in a Graph, use Node Name. When used standalone outside a Graph, manually set by the user. See "Injecting RunInfo" and "Using Components Standalone"
+ - Graph Node: Use Node Name `func WithNodeName(n string) GraphAddNodeOpt`
- Graph itself:
- Top-level graph uses Graph Name `func WithGraphName(graphName string) GraphCompileOption`
- - Nested internal graphs use the Node Name added when joining the parent graph
-- Type: Determined by the specific component implementation:
- - Components with interfaces: If Typer interface is implemented, uses GetType() method result. Otherwise uses reflection to get Struct/Func name.
- - Lambda: If Type is specified with `func WithLambdaType(t string) LambdaOpt`, uses that, otherwise empty string.
- - Graph Node: Uses the value of internal Component/Lambda/Graph.
+ - Internal nested graphs use the Node Name added when joining the parent graph
+- Type: Specified by the specific component implementation:
+ - Components with interfaces: If Typer interface is implemented, use the result of GetType() method. Otherwise use reflection to get Struct/Func name.
+ - Lambda: If Type is specified using `func WithLambdaType(t string) LambdaOpt`, use that, otherwise it's an empty string.
+ - Graph Node: Use the value of the internal Component/Lambda/Graph.
- Graph itself: Empty string.
- Component:
- Components with interfaces: Whatever interface it is
- Lambda: Fixed value Lambda
- - Graph Node: Uses the value of internal Component/Lambda/Graph.
+ - Graph Node: Use the value of the internal Component/Lambda/Graph.
- Graph itself: Fixed value Graph / Chain / Workflow. (Previously there were StateGraph / StateChain, now integrated into Graph / Chain)
### Callback Input & Output
-Essentially any type, because different Components have completely different inputs/outputs and internal states.
+Essentially any type, because different Components have completely different inputs, outputs, and internal states.
```go
type CallbackInput any
type CallbackOutput any
```
-For specific components, there are more specific types, such as Chat Model:
+For specific components, there are more specific types, such as Chat Model
```go
// CallbackInput is the input for the model callback.
@@ -133,61 +133,61 @@ type CallbackOutput struct {
}
```
-In specific implementations of Chat Model, such as OpenAI Chat Model, component authors are recommended to pass specific Input/Output types to Callback Handlers, rather than Any. This exposes more specific, customized intermediate state information.
+In specific implementations of Chat Model, such as OpenAI Chat Model, it's recommended for component authors to pass specific Input/Output types to the Callback Handler, rather than Any. This allows exposing more specific, customized intermediate state information.
-If a Graph Node triggers the Callback, since the Node cannot access the component's internal intermediate state information, it can only get the inputs and outputs defined in the component interface, so that's all it can give to the Callback Handler. For Chat Model, that's []*schema.Message and *schema.Message.
+If a Graph Node triggers the Callback, since the Node cannot get the component's internal intermediate state information and can only get the input and output specified in the component interface, it can only give these to the Callback Handler. For Chat Model, it's []*schema.Message and *schema.Message.
-When Graph itself triggers Callback, the input and output are the overall input and output of the Graph.
+When the Graph itself triggers the Callback, the input and output are the overall input and output of the Graph.
## Injecting Handlers
Handlers need to be injected into the Context to be triggered.
-### Injecting Handlers Globally
+### Injecting Global Handlers
-Inject global Handlers through `callbacks.AppendGlobalHandlers`. After injection, all callback triggering behaviors will automatically trigger these global Handlers. Typical scenarios are globally consistent, business-scenario-independent functions like tracing and logging.
+Inject global Handlers through `callbacks.AppendGlobalHandlers`. After injection, all callback triggering behaviors will automatically trigger these global Handlers. Typical scenarios are global consistent, business-scenario-agnostic features like tracing and logging.
-Not concurrency-safe. It's recommended to inject once during service initialization.
+Not concurrency safe. It's recommended to inject once during service initialization.
### Injecting Handlers into Graph
-Inject Handlers at graph runtime through `compose.WithCallbacks`, these Handlers will take effect for the entire current run of the graph, including all Nodes within the Graph and the Graph itself (as well as all nested graphs).
+Inject Handlers at graph runtime through `compose.WithCallbacks`. These Handlers will take effect on the entire run of the graph, including all Nodes within the Graph and the Graph itself (as well as all nested graphs).
-Inject Handlers to a specific Node of the top-level Graph through `compose.WithCallbacks(...).DesignateNode(...)`. When this Node itself is a nested Graph, it will be injected into this nested Graph itself and its internal Nodes.
+Inject Handlers to a specific Node of the top-level Graph through `compose.WithCallbacks(...).DesignateNode(...)`. When this Node itself is a nested Graph, it will be injected into the nested Graph itself and its internal Nodes.
Inject Handlers to a specific Node of an internally nested Graph through `compose.WithCallbacks(...).DesignateNodeWithPath(...)`.
### Injecting Handlers Outside Graph
-If you don't want to use Graph but want to use Callbacks:
+If you don't want to use Graph but want to use Callbacks, then:
-Obtain a new Context and inject Handlers and RunInfo through `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)`.
+Get a new Context through `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)` and inject Handlers and RunInfo.
### Handler Inheritance
-Same as child Context inheriting all Values from parent Context, child Context also inherits all Handlers from parent Context. For example, if the Context passed in when running a Graph already has Handlers, these Handlers will be inherited and take effect for this entire Graph run.
+Similar to how a child Context inherits all Values from the parent Context, a child Context also inherits all Handlers from the parent Context. For example, if the Context passed when running a Graph already has Handlers, these Handlers will be inherited and take effect throughout this run of the entire Graph.
## Injecting RunInfo
-RunInfo also needs to be injected into the Context to be provided to the Handler when callbacks are triggered.
+RunInfo also needs to be injected into the Context to be given to the Handler when triggering callbacks.
-### Graph-Managed RunInfo
+### Graph Managed RunInfo
Graph automatically injects RunInfo for all internal Nodes. The mechanism is that each Node's execution is a new child Context, and Graph injects the corresponding Node's RunInfo into this new Context.
### Injecting RunInfo Outside Graph
-If you don't want to use Graph but want to use Callbacks:
+If you don't want to use Graph but want to use Callbacks, then:
-Obtain a new Context and inject Handlers and RunInfo through `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)`.
+Get a new Context through `InitCallbacks(ctx context.Context, info *RunInfo, handlers ...Handler)` and inject Handlers and RunInfo.
-Obtain a new Context through `ReuseHandlers(ctx context.Context, info *RunInfo)`, reusing the Handlers in the previous Context and setting new RunInfo.
+Get a new Context through `ReuseHandlers(ctx context.Context, info *RunInfo)` to reuse the Handlers in the previous Context and set new RunInfo.
## Triggering Methods
-### Component Implementation Internal Triggering (Component Callback)
+### Triggering Inside Component Implementation (Component Callback)
In the component implementation code, call `OnStart(), OnEnd(), OnError(), OnStartWithStreamInput(), OnEndWithStreamOutput()` from the callbacks package. Taking Ark's ChatModel implementation as an example, in the Generate method:
@@ -259,14 +259,14 @@ func (cm *ChatModel) Stream(ctx context.Context, in []*schema.Message, opts ...f
}
```
-You can see that Generate call triggers OnEnd, while Stream call triggers OnEndWithStreamOutput:
+As you can see, Generate calls trigger OnEnd, while Stream calls trigger OnEndWithStreamOutput:
-When triggering Callbacks inside component implementations:
+When triggering Callbacks inside component implementation:
-- **When component input is StreamReader, trigger OnStartWithStreamInput, otherwise trigger OnStart**
-- **When component output is StreamReader, trigger OnEndWithStreamOutput, otherwise trigger OnEnd**
+- **When the component input is StreamReader, trigger OnStartWithStreamInput, otherwise trigger OnStart**
+- **When the component output is StreamReader, trigger OnEndWithStreamOutput, otherwise trigger OnEnd**
-Components that implement callback triggering internally should implement the Checker interface, with IsCallbacksEnabled returning true, to communicate "I have implemented callback triggering internally" to the outside:
+Components that implement callback triggering internally should implement the Checker interface, with IsCallbacksEnabled returning true, to communicate "I have implemented callback triggering internally":
```go
// Checker tells callback aspect status of component's implementation
@@ -277,32 +277,32 @@ type Checker interface {
}
```
-If a component implementation doesn't implement the Checker interface, or IsCallbacksEnabled returns false, it can be assumed that the component doesn't trigger callbacks internally, and Graph Node needs to be responsible for injection and triggering (when used within a Graph).
+If a component implementation doesn't implement the Checker interface, or IsCallbacksEnabled returns false, it can be assumed that the component doesn't trigger callbacks internally, and the Graph Node needs to be responsible for injection and triggering (when used within a Graph).
### Graph Node Triggering (Node Callback)
-When a Component is orchestrated into a Graph, it becomes a Node. At this point, if the Component itself triggers callbacks, the Node reuses the Component's callback handling. Otherwise, the Node wraps callback handler trigger points around the Component. These points correspond to the Component's streaming paradigm. For example, a ChatModelNode wraps OnStart/OnEnd/OnError around the Generate method, and OnStart/OnEndWithStreamOutput/OnError around the Stream method.
+When a Component is orchestrated into a Graph, it becomes a Node. At this point, if the Component itself triggers callbacks, the Node reuses the Component's callback handling. Otherwise, the Node wraps callback handler trigger points around the Component. These points correspond to the Component's own streaming paradigms. For example, a ChatModelNode will have OnStart/OnEnd/OnError wrapped around the Generate method, and OnStart/OnEndWithStreamOutput/OnError wrapped around the Stream method.
-At Graph runtime, components run in Invoke or Transform paradigm, and based on the component's specific business streaming paradigm, call the corresponding component methods. For example, when Graph runs with Invoke, Chat Model Node runs with Invoke, calling the Generate method. When Graph runs with Stream, Chat Model Node runs with Transform, but since Chat Model's business streaming paradigm doesn't have Transform, it automatically falls back to calling the Stream method. Therefore:
+When the Graph runs, each component runs in either Invoke or Transform paradigm, and calls the corresponding component method based on the component's specific business streaming paradigm. For example, when Graph runs with Invoke, the Chat Model Node runs with Invoke, calling the Generate method. When Graph runs with Stream, the Chat Model Node runs with Transform, but since Chat Model's business streaming paradigm doesn't have Transform, it automatically falls back to calling the Stream method. Therefore:
-**Which timing point (OnStart vs OnStartWithStreamInput) a Graph Node specifically triggers depends on two factors: the component implementation's business streaming paradigm and the Graph's execution mode.**
+**Which specific point the Graph Node triggers (OnStart or OnStartWithStreamInput) depends on both the component implementation's business streaming paradigm and the Graph's running method.**
-For detailed introduction to Eino streaming programming, see [Eino Streaming Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
+For a detailed introduction to Eino streaming programming, see [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
### Graph Self-Triggering (Graph Callback)
-Graph triggers Callback Handlers at its own start, end, and error timings. If Graph is called in Invoke form, it triggers OnStart/OnEnd/OnError. If called in Stream/Collect/Transform form, it triggers OnStartWithStreamInput/OnEndWithStreamOutput/OnError. This is because **Graph internally always executes as Invoke or Transform**. See [Eino Streaming Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
+Graph triggers Callback Handlers at its own start, end, and error timings. If Graph is called with Invoke, it triggers OnStart/OnEnd/OnError. If called with Stream/Collect/Transform, it triggers OnStartWithStreamInput/OnEndWithStreamOutput/OnError. This is because **Graph internally always executes with Invoke or Transform**. See [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
-It's worth noting: graph is also a type of component, so graph callback is also a special form of component callback. According to the Node Callback definition, when the component inside a Node implements awareness and handling of triggering timings, the Node directly reuses the Component's implementation and won't implement Node Callback. This means when a graph is added to another Graph as a Node via AddGraphNode, this Node reuses the internal graph's graph callback.
+It's worth noting that: graph is also a type of component, so graph callback is also a special form of component callback. According to the Node Callback definition, when the component inside a Node has implemented awareness and handling of triggering timing, the Node will directly reuse the Component's implementation and won't implement Node Callback again. This means when a graph is added to another Graph as a Node through AddGraphNode, this Node will reuse the internal graph's graph callback.
## Parsing Callback Input & Output
-From the above, we know that Callback Input & Output's underlying type is Any, but different component types may pass their own specific types when actually triggering callbacks. And in the Callback Handler interface definition, the parameters of each method are also Any-typed Callback Input & Output.
+As mentioned above, the underlying type of Callback Input & Output is Any, but different component types may pass their own specific types when actually triggering callbacks. And in the Callback Handler interface definition, the parameters of each method are also Any type Callback Input & Output.
Therefore, specific Handler implementations need to do two things:
-1. Determine which component type is currently triggering the callback based on RunInfo, such as RunInfo.Component == "ChatModel", or RunInfo.Type == "xxx Chat Model".
-2. Convert the any-typed Callback Input & Output to the corresponding specific type, taking RunInfo.Component == "ChatModel" as an example:
+1. Determine which component type currently triggered the callback based on RunInfo, such as RunInfo.Component == "ChatModel", or RunInfo.Type == "xxx Chat Model".
+2. Convert the any type Callback Input & Output to the corresponding specific type. Taking RunInfo.Component == "ChatModel" as an example:
```go
// ConvCallbackInput converts the callback input to the model callback input.
@@ -334,7 +334,7 @@ func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput {
}
```
-If the Handler needs to add switch cases to determine RunInfo.Component, and for each case, call the corresponding conversion function to convert Any to a specific type, it's indeed somewhat complex. To reduce the repetitive work of writing glue code, we provide two convenient tool functions for implementing Handlers.
+If the Handler needs to add switch cases to determine RunInfo.Component, and for each case, call the corresponding conversion function to convert Any to a specific type, it's indeed complex. To reduce the repetitive work of writing glue code, we provide two convenient utility functions for implementing Handlers.
## Handler Implementation Methods
@@ -342,7 +342,7 @@ Besides directly implementing the Handler interface, Eino provides two convenien
### HandlerHelper
-If the user's Handler only focuses on specific component types, such as in ReActAgent scenarios only focusing on ChatModel and Tool, it's recommended to use HandlerHelper to quickly create typed Callback Handlers:
+If the user's Handler only focuses on specific component types, such as the ReactAgent scenario, only focusing on ChatModel and Tool, it's recommended to use HandlerHelper to quickly create specifically typed Callback Handlers:
```go
import ucb "github.com/cloudwego/eino/utils/callbacks"
@@ -350,7 +350,7 @@ import ucb "github.com/cloudwego/eino/utils/callbacks"
handler := ucb.NewHandlerHelper().ChatModel(modelHandler).Tool(toolHandler).Handler()
```
-Where modelHandler is Chat Model component's further encapsulation of callback handler:
+Where modelHandler is a further encapsulation of the callback handler for the Chat Model component:
```go
// from package utils/callbacks
@@ -364,15 +364,15 @@ type ModelCallbackHandler struct {
}
```
-The above ModelCallbackHandler encapsulates three operations:
+The ModelCallbackHandler above encapsulates three operations:
-1. No longer need to determine RunInfo.Component to select callbacks triggered by ChatModel, as automatic filtering is already done.
-2. Only requires implementing the triggering timings supported by Chat Model component, here removing the unsupported OnStartWithStreamInput. Also, if the user only cares about some of the four timings supported by Chat Model, such as only OnStart, they can implement only OnStart.
-3. Input / Output are no longer Any types, but already converted model.CallbackInput, model.CallbackOutput.
+1. No longer need to check RunInfo.Component to select callbacks triggered by ChatModel, as automatic filtering is already done.
+2. Only requires implementing triggering timings supported by the Chat Model component. Here the unsupported OnStartWithStreamInput is removed. Also, if the user only cares about some of the four timings supported by Chat Model, such as only OnStart, they can implement only OnStart.
+3. Input/Output are no longer Any type, but already converted model.CallbackInput, model.CallbackOutput.
-HandlerHelper supports all official components, the current list is: ChatModel, ChatTemplate, Retriever, Indexer, Embedding, Document.Loader, Document.Transformer, Tool, ToolsNode.
+HandlerHelper supports all official components. The current list is: ChatModel, ChatTemplate, Retriever, Indexer, Embedding, Document.Loader, Document.Transformer, Tool, ToolsNode.
-For Lambda, Graph, Chain and other "components" with uncertain input/output types, HandlerHelper can also be used, but can only achieve point 1 above, i.e., automatic filtering by component type, points 2 and 3 still need to be implemented by the user:
+For "components" like Lambda, Graph, Chain whose input and output types are uncertain, HandlerHelper can also be used, but only achieves point 1 above, i.e., automatic filtering by component type. Points 2 and 3 still need to be implemented by the user:
```go
import ucb "github.com/cloudwego/eino/utils/callbacks"
@@ -380,11 +380,11 @@ import ucb "github.com/cloudwego/eino/utils/callbacks"
handler := ucb.NewHandlerHelper().Lambda(callbacks.Handler).Graph(callbacks.Handler)...Handler()
```
-At this point, NewHandlerHelper().Lambda() needs to pass in callbacks.Handler which can be implemented using the HandlerBuilder below.
+In this case, the callbacks.Handler passed to NewHandlerHelper().Lambda() can be implemented using HandlerBuilder below.
### HandlerBuilder
-If the user's Handler needs to focus on multiple component types, but only needs to focus on some triggering timings, HandlerBuilder can be used:
+If the user's Handler needs to focus on multiple component types but only needs to focus on some triggering timings, HandlerBuilder can be used:
```go
import "github.com/cloudwego/eino/callbacks"
@@ -437,7 +437,7 @@ func main() {
}
```
-- Inject Handlers at runtime through WithHandlers option, and specify effective Nodes / nested internal Graphs / internal Graph Nodes through DesignateNode or DesignateNodeByPath.
+- Inject Handlers at runtime through WithHandlers option, specify effective Nodes / nested internal Graphs / internal Graph's Nodes through DesignateNode or DesignateNodeByPath.
```go
package main
@@ -482,9 +482,9 @@ func main() {
### Using Outside Graph
-This scenario is: not using Graph/Chain/Workflow orchestration capabilities, but directly calling ChatModel/Tool/Lambda and other components with code, and hoping these components can successfully trigger Callback Handlers.
+This scenario is: not using orchestration capabilities like Graph/Chain/Workflow, but calling ChatModel/Tool/Lambda and other components directly with code, and hoping these components can successfully trigger Callback Handlers.
-The problem users need to solve in this scenario is: manually setting correct RunInfo and Handlers, because there's no Graph to help users automatically set RunInfo and Handlers.
+The problem users need to solve in this scenario is: manually setting the correct RunInfo and Handlers, because there's no Graph to automatically set RunInfo and Handlers for the user.
Complete example:
@@ -499,7 +499,7 @@ import (
)
func innerLambda(ctx context.Context, input string) (string, error) {
- // As ComponentB's implementer: add default RunInfo when entering the component (Name cannot be given a default value)
+ // As the implementer of ComponentB: add default RunInfo when entering the component (Name cannot be given a default value)
ctx = callbacks.EnsureRunInfo(ctx, "Lambda", compose.ComponentOfLambda)
ctx = callbacks.OnStart(ctx, input)
out := "inner:" + input
@@ -508,18 +508,18 @@ func innerLambda(ctx context.Context, input string) (string, error) {
}
func outerLambda(ctx context.Context, input string) (string, error) {
- // As ComponentA's implementer: add default RunInfo when entering the component
+ // As the implementer of ComponentA: add default RunInfo when entering the component
ctx = callbacks.EnsureRunInfo(ctx, "Lambda", compose.ComponentOfLambda)
ctx = callbacks.OnStart(ctx, input)
- // Recommended: replace RunInfo before calling, ensuring inner component gets correct name/type/component
+ // Recommended: replace RunInfo before calling, ensure inner component gets correct name/type/component
ctxInner := callbacks.ReuseHandlers(ctx,
&callbacks.RunInfo{Name: "ComponentB", Type: "Lambda", Component: compose.ComponentOfLambda},
)
- out1, _ := innerLambda(ctxInner, input) // inner RunInfo.Name = "ComponentB"
+ out1, _ := innerLambda(ctxInner, input) // Inner RunInfo.Name = "ComponentB"
- // Without replacement: framework clears RunInfo, can only rely on EnsureRunInfo to add default values (Name is empty)
- out2, _ := innerLambda(ctx, input) // inner RunInfo.Name == ""
+ // Not replaced: framework clears RunInfo, can only rely on EnsureRunInfo to add default values (Name is empty)
+ out2, _ := innerLambda(ctx, input) // Inner RunInfo.Name == ""
final := out1 + "|" + out2
ctx = callbacks.OnEnd(ctx, final)
@@ -538,21 +538,21 @@ func main() {
}
```
-Explanation of the above sample code:
+Explanation of the sample code above:
-- Initialization: When using components outside graph/chain, use InitCallbacks to set the first RunInfo and Handlers, so subsequent component execution can get the complete callback context.
-- Internal calls: Before component A calls component B internally, use ReuseHandlers to replace RunInfo (keeping original handlers), ensuring B's callbacks get correct Type/Component/Name.
-- Consequences of not replacing: After a complete set of Callbacks is triggered, Eino clears the RunInfo in the current ctx. At this point, because RunInfo is empty, Eino won't trigger Callbacks anymore; Component B's developer can only use EnsureRunInfo in their own implementation to add default values for Type/Component, to ensure RunInfo is non-empty and roughly correct, thus successfully triggering Callbacks. But cannot give a reasonable Name, so RunInfo.Name will be an empty string.
+- Initialization: When using components outside graph/chain, use InitCallbacks to set the first RunInfo and Handlers, so subsequent component execution can get complete callback context.
+- Internal calls: Before calling component B inside component A, use ReuseHandlers to replace RunInfo (retaining original handlers), ensuring B's callback gets correct Type/Component/Name.
+- Consequences of not replacing: After a complete set of Callbacks is triggered, Eino clears the RunInfo in the current ctx. At this point, because RunInfo is empty, Eino will no longer trigger Callbacks; component B's developer can only use EnsureRunInfo in their own implementation to add default values for Type/Component to ensure RunInfo is non-empty and roughly correct, thus successfully triggering Callbacks. But cannot give a reasonable Name, so RunInfo.Name will be an empty string.
-### Component Nesting Usage
+### Nested Component Usage
-Scenario: Inside one component, such as Lambda, manually call another component, such as ChatModel.
+Scenario: Manually calling another component, such as ChatModel, inside a component, such as Lambda.
At this point, if the outer component's ctx has callback handlers, because this ctx is also passed to the inner component, the inner component will also receive the same callback handlers.
Distinguishing by "whether you want the inner component to trigger callbacks":
-1. Want to trigger: Basically equivalent to the situation in the previous section, it's recommended to manually set `RunInfo` for the inner component through `ReuseHandlers`.
+1. Want to trigger: Basically the same as the previous section, it's recommended to manually set `RunInfo` for the inner component through `ReuseHandlers`.
```go
package main
@@ -570,17 +570,17 @@ import (
// Outer Lambda, manually calls ChatModel inside
func OuterLambdaCallsChatModel(cm model.BaseChatModel) *compose.Lambda {
return compose.InvokableLambda(func(ctx context.Context, input string) (string, error) {
- // 1) Reuse outer handlers and explicitly set RunInfo for inner component
+ // 1) Reuse outer handlers, and explicitly set RunInfo for inner component
innerCtx := callbacks.ReuseHandlers(ctx, &callbacks.RunInfo{
- Type: "InnerCM", // Customizable
- Component: components.ComponentOfChatModel, // Mark component type
- Name: "inner-chat-model", // Customizable
+ Type: "InnerCM", // customizable
+ Component: components.ComponentOfChatModel, // mark component type
+ Name: "inner-chat-model", // customizable
})
- // 2) Build input messages
+ // 2) Construct input messages
msgs := []*schema.Message{{Role: schema.User, Content: input}}
- // 3) Call ChatModel (internally triggers corresponding callbacks)
+ // 3) Call ChatModel (will trigger corresponding callbacks internally)
out, err := cm.Generate(innerCtx, msgs)
if err != nil {
return "", err
@@ -590,19 +590,19 @@ func OuterLambdaCallsChatModel(cm model.BaseChatModel) *compose.Lambda {
}
```
-The above code assumes "the inner ChatModel's Generate method internally has already called OnStart, OnEnd, OnError methods". If not, you need to call these methods "on behalf of the inner component" inside the outer component:
+The code above assumes "the inner ChatModel's Generate method has already called OnStart, OnEnd, OnError internally". If not, you need to call these methods "on behalf of the inner component" inside the outer component:
```go
func OuterLambdaCallsChatModel(cm model.BaseChatModel) *compose.Lambda {
return compose.InvokableLambda(func(ctx context.Context, input string) (string, error) {
- // Reuse outer handlers and explicitly set RunInfo for inner component
+ // Reuse outer handlers, and explicitly set RunInfo for inner component
ctx = callbacks.ReuseHandlers(ctx, &callbacks.RunInfo{
Type: "InnerCM",
Component: components.ComponentOfChatModel,
Name: "inner-chat-model",
})
- // Build input messages
+ // Construct input messages
msgs := []*schema.Message{{Role: schema.User, Content: input}}
// Explicitly trigger OnStart
@@ -624,7 +624,7 @@ func OuterLambdaCallsChatModel(cm model.BaseChatModel) *compose.Lambda {
}
```
-1. Don't want to trigger: This assumes the inner component implements `IsCallbacksEnabled()` and returns true, and internally calls `EnsureRunInfo`. At this point, inner callbacks will trigger by default. If you don't want them to trigger, the simplest way is to remove handlers from ctx, such as passing a new ctx to the inner component:
+1. Don't want to trigger: This assumes the inner component implements `IsCallbacksEnabled()` and returns true, and calls `EnsureRunInfo` internally. At this point, inner callbacks will trigger by default. If you don't want to trigger, the simplest way is to remove the handler from ctx, such as passing a new ctx to the inner component:
```go
package main
@@ -652,7 +652,7 @@ func OuterLambdaCallsChatModel(cm model.BaseChatModel) *compose.Lambda {
}
```
- 1. But sometimes users may want to "only not trigger a specific callback handler, but still trigger other callback handlers". The recommended approach is to add code in this callback handler to filter out inner components by RunInfo:
+ 1. But sometimes users may want to "not trigger a specific callback handler, but still trigger other callback handlers". The recommended approach is to add code in this callback handler to filter out the inner component based on RunInfo:
```go
package main
@@ -673,7 +673,7 @@ func newSelectiveHandler() callbacks.Handler {
OnStartFn(func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
if info != nil && info.Component == components.ComponentOfChatModel &&
info.Type == "InnerCM" && info.Name == "inner-chat-model" {
- // Filter target: inner ChatModel, return directly without processing
+ // Filter target: inner ChatModel, return directly, do nothing
return ctx
}
log.Printf("[OnStart] %s/%s (%s)", info.Type, info.Name, info.Component)
@@ -682,7 +682,7 @@ func newSelectiveHandler() callbacks.Handler {
OnEndFn(func(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {
if info != nil && info.Component == components.ComponentOfChatModel &&
info.Type == "InnerCM" && info.Name == "inner-chat-model" {
- // Filter target: inner ChatModel, return directly without processing
+ // Filter target: inner ChatModel, return directly, do nothing
return ctx
}
log.Printf("[OnEnd] %s/%s (%s)", info.Type, info.Name, info.Component)
@@ -691,12 +691,12 @@ func newSelectiveHandler() callbacks.Handler {
Build()
}
-// Composition example: outer call wants to trigger, specific handler filters out inner ChatModel through RunInfo
+// Combination example: outer call wants to trigger, specific handler filters out inner ChatModel through RunInfo
func Example(cm model.BaseChatModel) (compose.Runnable[string, string], error) {
handler := newSelectiveHandler()
chain := compose.NewChain[string, string]().
- AppendLambda(OuterLambdaCallsChatModel(cm)) // Internally will ReuseHandlers + RunInfo
+ AppendLambda(OuterLambdaCallsChatModel(cm)) // Will ReuseHandlers + RunInfo internally
return chain.Compile(
context.Background(),
@@ -708,30 +708,30 @@ func Example(cm model.BaseChatModel) (compose.Runnable[string, string], error) {
### Reading and Writing Input & Output in Handler
-When input & output flow through the graph, they are direct variable assignments. As shown in the figure below, NodeA.Output, NodeB.Input, NodeC.Input, and the input & output obtained in each Handler, if they are reference types like struct pointers or Maps, they are all the same piece of data. Therefore, whether in Node or Handler, it's not recommended to modify Input & Output, as it will cause concurrency issues: even in synchronous situations, Node B and Node C are concurrent, causing internal handler1 and handler2 to be concurrent. When there's asynchronous processing logic, there are more possible concurrency scenarios.
+Input & output flow in the graph through direct variable assignment. As shown in the figure below, NodeA.Output, NodeB.Input, NodeC.Input, and the input & output obtained in each Handler, if they are reference types like struct pointers or Maps, are all the same piece of data. Therefore, modifying Input & Output is not recommended in either Node or Handler, as it will cause concurrency issues: even in synchronous situations, Node B and Node C may be concurrent, causing handler1 and handler2 to be concurrent. With asynchronous processing logic, there are more possible concurrency scenarios.
-In stream passing scenarios, all downstream nodes and handlers' input streams are streams obtained from StreamReader.Copy(n), which can be read independently. However, each chunk in the stream is direct variable assignment, if the chunk is a reference type like struct pointer or Map, each copied stream reads the same piece of data. Therefore, in Node and Handler, it's also not recommended to modify stream chunks, there are concurrency issues.
+In stream passing scenarios, input streams in all downstream nodes and handlers are streams obtained from StreamReader.Copy(n), which can be read independently. However, each chunk in the stream is direct variable assignment. If the chunk is a reference type like struct pointer or Map, the same piece of data is read by each copied stream. Therefore, modifying stream chunks in Node and Handler is also not recommended due to concurrency issues.
### Passing Information Between Handlers
-Between different timings of the same Handler, information can be passed through ctx, such as returning a new context through context.WithValue in OnStart, and then retrieving this value from context in OnEnd.
+Information can be passed between different timings of the same Handler through ctx, such as returning a new context through context.WithValue in OnStart, and retrieving this value from the context in OnEnd.
-Between different Handlers, there's no guarantee of execution order, so it's not recommended to pass information between different Handlers through the above mechanism. Essentially, there's no guarantee that the context returned by one Handler will definitely enter the function execution of the next Handler.
+Between different Handlers, there's no guaranteed execution order, so passing information between different Handlers through the above mechanism is not recommended. Essentially, there's no guarantee that the context returned by one Handler will definitely enter the function execution of the next Handler.
-If you need to pass information between different Handlers, the recommended approach is to set a global, request-scoped variable in the outermost context (such as the context passed in when graph executes) as a shared space for storing and retrieving common information, and read and update this shared variable as needed in each Handler. Users need to ensure the concurrency safety of this shared variable themselves.
+If information needs to be passed between different Handlers, the recommended approach is to set a global, request-level variable in the outermost context (such as the context passed when executing the graph) as a common information storage space, and read and update this common variable as needed in each Handler. Users need to ensure the concurrency safety of this common variable themselves.
-### Remember to Close Streams
+### Streams Must Be Closed
-Taking the existence of ChatModel, a node with true streaming output, as an example, when there are Callback aspects, ChatModel's output stream:
+Taking the presence of nodes like ChatModel that have true stream output as an example, when there are Callback aspects, the ChatModel's output stream:
-- Needs to be consumed by downstream nodes as input, and also consumed by Callback aspects
-- A frame (Chunk) in a stream can only be consumed by one consumer, i.e., streams are not broadcast models
+- Needs to be consumed both by downstream nodes as input and by Callback aspects
+- A frame (Chunk) in a stream can only be consumed by one consumer, i.e., streams are not broadcast model
-So at this point, the stream needs to be copied, the copy relationship is as follows:
+So the stream needs to be copied at this point, with the copy relationship as follows:
diff --git a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/checkpoint_interrupt.md b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/checkpoint_interrupt.md
index 2a2f01ead29..0fa96f3c947 100644
--- a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/checkpoint_interrupt.md
+++ b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/checkpoint_interrupt.md
@@ -1,27 +1,29 @@
---
Description: ""
-date: "2026-01-20"
+date: "2026-03-02"
lastmod: ""
tags: []
-title: 'Eino: Interrupt & CheckPoint Manual'
+title: 'Eino: Interrupt & CheckPoint User Manual'
weight: 7
---
> 💡
-> Note: A bug in v0.3.26 broke CheckPoint serialization. For new CheckPoint usage, use v0.3.26+ (preferably latest).
+> Note: In version v0.3.26, due to a code writing error, the serialization content of CheckPoint broke compatibility. For new integrations using CheckPoint after v0.3.26, it's recommended to use the latest version directly.
>
-> Eino provides a compatibility branch for users with pre-v0.3.26 checkpoints to migrate; once old data is invalidated, upgrade to mainline. The branch incurs overhead and is not merged due to typical short checkpoint lifetimes.
+> Eino provides a compatibility branch. Businesses that use checkpoint and are on versions lower than v0.3.26 should first upgrade to the compatibility branch when upgrading eino, then upgrade to the main branch after old data expires.
+>
+> Since the compatibility branch introduces additional performance overhead and generally business agent checkpoints have a relatively short validity period, the branch has not been merged into the main branch.
## Introduction
-`Interrupt & CheckPoint` lets you pause a Graph at specified locations and resume later. For `StateGraph`, you can modify `State` before resuming.
+Using the Interrupt & CheckPoint feature, you can pause Graph execution at specified positions and resume from the breakpoint later. For StateGraph, you can also modify State before resuming from the breakpoint.
> 💡
-> Resuming restores inputs and per-node runtime data. Ensure the Graph orchestration is identical and pass the same CallOptions again (unless you explicitly rely on CallOptions to carry resume-time data).
+> Breakpoint resumption can only restore input and data generated by each node at runtime. You need to ensure the Graph orchestration is exactly the same, and pass in CallOptions completely again (should be kept consistent unless there are special circumstances, except for cases like relying on CallOption to pass data during Resume).
## Using Static Interrupt
-Static Interrupt supports pausing before or after specified nodes. Set `WithInterruptAfterNodes` and `WithInterruptBeforeNodes` at compile:
+Static Interrupt supports pausing the Graph before or after a specified Node executes. Pass in WithInterruptAfterNodes and WithInterruptBeforeNodes Options during Compile to set Interrupts:
```go
import (
@@ -30,9 +32,9 @@ import (
func main() {
g := NewGraph[string, string]()
- err := g.AddLambdaNode("node1", compose.InvokableLambda(func(ctx context.Context, input string) (output string, err error) {/*invokable func*/})
+ err := g.AddLambdaNode("node1", compose.InvokableLambda(func(ctx **context**._Context_, input string) (output string, err error) {/*invokable func*/})
if err != nil {/* error handle */}
- err = g.AddLambdaNode("node2", compose.InvokableLambda(func(ctx context.Context, input string) (output string, err error) {/*invokable func*/})
+ err = g.AddLambdaNode("node2", compose.InvokableLambda(func(ctx **context**._Context_, input string) (output string, err error) {/*invokable func*/})
if err != nil {/* error handle */}
/** other graph composed code
@@ -45,27 +47,27 @@ func main() {
```
> 💡
-> Tip: Currently only compile-time static breakpoints are supported. If you need request-time configuration, please open an issue.
+> Currently only static breakpoints can be set during Compile. If you need to set them during requests, feel free to raise the requirement~
-Extract interrupt info from the run error:
+You can get whether the current run was Interrupted and the Interrupt information from the error returned by the run:
```go
// compose/checkpoint.go
-type InterruptInfo struct {
+**type **InterruptInfo **struct **{
State any
BeforeNodes []string
AfterNodes []string
RerunNodes []string
- RerunNodesExtra map[string]any
- SubGraphs map[string]*InterruptInfo
+ RerunNodesExtra **map**[string]any
+ SubGraphs **map**[string]*InterruptInfo
InterruptContexts []*InterruptCtx
}
func ExtractInterruptInfo(err error) (info *InterruptInfo, existed bool) {}
```
-Example:
+For example:
```go
import "github.com/cloudwego/eino/compse"
@@ -86,28 +88,28 @@ if err != nil {
```
> 💡
-> During interrupt, the output is empty and should be ignored.
+> When Interrupted, output is an empty value and has no meaning.
## Using CheckPoint
-CheckPoint records Graph runtime state to support resuming.
+CheckPoint records Graph running state. Using CheckPoint allows resuming execution after Interrupt.
-### Implement CheckpointStore
+### Implementing CheckPointStore
-`CheckpointStore` is a KV storage with key type `string` and value type `[]byte`. Eino does not provide a default implementation; implement your own to persist checkpoints.
+CheckPointStore is a KV storage interface with key type string and value type []byte. We don't provide encapsulation or default implementation; users need to implement it themselves to store checkpoints.
```go
// compose/checkpoint.go
type CheckpointStore interface {
- Get(ctx context.Context, key string) (value []byte, existed bool,err error)
- Set(ctx context.Context, key string, value []byte) (err error)
+ Get(ctx **context**._Context_, key string) (value []byte, existed bool,err error)
+ Set(ctx **context**._Context_, key string, value []byte) (err error)
}
```
-### Register Types For Serialization
+### Registering Serialization Methods
-Saving and loading checkpoints involves serialization of node inputs/outputs and `State`. For simple or Eino built-in types (e.g., `Message`, `Document`), no action is needed. For custom structs, register types in advance with `schema.RegisterName`:
+CheckPoint saving and reading involves serialization and deserialization of Graph node inputs, outputs, and State. When only using simple types or eino built-in types (such as Message or Document), users don't need additional operations; when introducing custom structs, you need to register types in advance. Eino provides the registration method `schema.RegisterName`:
```go
package main
@@ -128,12 +130,12 @@ func init() {
}
```
-After registration, type metadata is included during serialization. On deserialization, Eino can restore the correct type even when the destination is `interface{}`. The key uniquely identifies the type; once chosen, do not change it, otherwise persisted checkpoints cannot be restored.
+After registration, type information will be additionally recorded during serialization, so during deserialization, even without specifying the type (such as deserializing to interface{}), Eino can deserialize to the correct type. The key in the registration method uniquely identifies this type. Once the key is determined, it must not be changed, otherwise persisted checkpoints cannot be correctly restored.
> 💡
-> Struct unexported fields are inaccessible and thus not stored/restored.
+> Unexported fields of structs cannot be accessed, so they won't be stored/restored
-By default, Eino uses its built-in serializer. If a registered type implements `json.Marshaler` and `json.Unmarshaler`, those custom methods are used.
+By default, eino's built-in serialization functionality is used. At this time, if the registered type implements json Marshaler and Unmarshaler, the type's serialization and deserialization will use custom methods.
```
// encoding/json
@@ -147,7 +149,7 @@ type Unmarshaler interface {
}
```
-Eino also provides an option to use `gob` serialization:
+Eino also provides an option to change the serialization method to gob:
```go
r, err := compose.NewChain[*AgentInput, Message]().
@@ -160,11 +162,11 @@ r, err := compose.NewChain[*AgentInput, Message]().
compose.WithSerializer(&gobSerializer{}))
```
-Choose based on preference; avoid switching later, as historical data will be incompatible.
+Users can choose based on preference. Once chosen, it's not recommended to change easily as historical data won't be compatible.
-### Enable CheckPoint
+### Enabling CheckPoint
-Bind `CheckpointStore` at compile and set interrupt nodes if needed:
+After creating CheckPointStore, pass it as an Option when Compiling Graph to bind CheckPointer to Graph:
```go
import (
@@ -181,7 +183,7 @@ func main() {
}
```
-At request time, provide a checkpoint ID:
+After that, you can introduce CheckPoint during requests through CallOption:
```
// compose/checkpoint.go
@@ -189,13 +191,13 @@ At request time, provide a checkpoint ID:
func WithCheckPointID(checkPointID string) Option
```
-The checkpoint ID is used as the `CheckpointStore` key. During execution, if the ID exists, the graph resumes from it; on interrupt, the graph stores its state under that ID.
+Checkpoint id will be used as the key for CheckPointStore. When the graph runs, it checks whether CheckPointStore has this id. If it exists, it resumes from the checkpoint; when interrupted, it saves the graph state to this id.
## Dynamic Interrupt
-Nodes can trigger dynamic interrupts by returning special errors.
+Nodes can dynamically trigger Interrupt by returning special errors:
-### Prior to Eino v0.7.0
+### Before eino v0.7.0
```
// eino/compose/interrupt.go
@@ -204,14 +206,16 @@ Nodes can trigger dynamic interrupts by returning special errors.
var InterruptAndRerun = errors.New("interrupt and rerun")
// emit an interrupt signal with extra info
-func NewInterruptAndRerunErr(extra any) error
+**func **NewInterruptAndRerunErr(extra any) error
```
-When the graph receives such an error, it interrupts. On resume, the node runs again; before rerun, `StateModifier` is applied if configured. The rerun's input is replaced with a zero value rather than the original; if the original input is needed, save it into `State` beforehand.
+When Eino Graph receives this error returned by a node, an interrupt will occur. When resuming, this node will be run again. Before re-running, StateModifier will be called to modify state (if configured).
+
+In this case, when re-running the node, the input will be replaced with an empty value instead of the original input. If the original input is still needed when re-running, it needs to be saved to State in advance.
-### From Eino v0.7.0 onward
+### In eino v0.7.0 and later
-Support is added for local state persistence, exposing inner interrupt signals, and parallel interrupts:
+Added support for "saving local state", "exposing internal interrupt signals", and "parallel interrupts":
```
// eino/compose/interrupt.go
@@ -229,40 +233,40 @@ func StatefulInterrupt(ctx context.Context, info any, state any) error
func CompositeInterrupt(ctx context.Context, info any, state any, errs ...error)
```
-See design details: [Eino human-in-the-loop framework: architecture guide](/docs/eino/core_modules/eino_adk/agent_hitl)
+For detailed design, see: [Eino human-in-the-loop Framework: Technical Architecture Guide](/docs/eino/core_modules/eino_adk/agent_hitl)
-## External Active Interrupt
+## External Proactive Interrupt
-Sometimes, we want to actively trigger an interrupt from outside the Graph, save the state, and resume later. These scenarios may include graceful instance shutdown, etc. In such cases, you can call `WithGraphInterrupt` to get a ctx and an interrupt function. The ctx is passed to `graph.Invoke()` and other run methods, while the interrupt function is called when you want to actively interrupt:
+Sometimes, we want to proactively trigger an interrupt from outside the Graph, save the scene, and resume later. These scenarios may include graceful instance shutdown, etc. In this case, you can call `WithGraphInterrupt` to get a ctx and an interrupt function. The ctx is passed to running methods like `graph.Invoke()`, and the interrupt function is called when the user wants to proactively interrupt:
```go
// from compose/graph_call_options.go
-// WithGraphInterrupt creates a context with graph cancellation support.
-// When the returned context is used to invoke a graph or workflow, calling the interrupt function will trigger an interrupt.
-// The graph will wait for current tasks to complete by default.
-func WithGraphInterrupt(parent context.Context) (ctx context.Context, interrupt func(opts ...GraphInterruptOption)) {}
+_// WithGraphInterrupt creates a context with graph cancellation support._
+_// When the returned context is used to invoke a graph or workflow, calling the interrupt function will trigger an interrupt._
+_// The graph will wait for current tasks to complete by default._
+**func **WithGraphInterrupt(parent context.Context) (ctx context.Context, interrupt **func**(opts ...GraphInterruptOption)) {}
```
-When actively calling the interrupt function, you can pass parameters such as timeout:
+When proactively calling the interrupt function, you can pass parameters like timeout:
```go
// from compose/graph_call_options.go
-// WithGraphInterruptTimeout specifies the max waiting time before generating an interrupt.
-// After the max waiting time, the graph will force an interrupt. Any unfinished tasks will be re-run when the graph is resumed.
-func WithGraphInterruptTimeout(timeout time.Duration) GraphInterruptOption {
- return func(o *graphInterruptOptions) {
+_// WithGraphInterruptTimeout specifies the max waiting time before generating an interrupt._
+_// After the max waiting time, the graph will force an interrupt. Any unfinished tasks will be re-run when the graph is resumed._
+**func **WithGraphInterruptTimeout(timeout time.Duration) GraphInterruptOption {
+ **return func**(o *graphInterruptOptions) {
o.timeout = &timeout
}
}
```
-When an external interrupt is triggered, the node has no opportunity to save local state (including the node's input), so Eino automatically saves the input of externally interrupted nodes and restores it on the next execution. For non-external interrupt scenarios, when a node initiates an interrupt internally, saving the input is the responsibility of each node, which can be done by saving to the graph state or using `compose.StatefulInterrupt` to save local state.
+When externally triggered interrupt occurs, nodes internally don't have the opportunity to save local state (including the node's input), so eino will automatically save the input of externally interrupted nodes and automatically restore it on the next execution. For non-externally triggered interrupt scenarios, saving input is each node's responsibility when initiating interrupt internally, which can be done by saving to graph state or using `compose.StatefulInterrupt` to save local state.
## CheckPoint in Streaming
-Streaming checkpoints require concatenation of chunks. Register a concat function:
+Streaming needs to concatenate data streams when saving CheckPoint, so concatenation methods need to be registered:
```go
// compose/stream_concat.go
@@ -273,7 +277,7 @@ type TestStruct struct {
Body string
}
-// RegisterStreamChunkConcatFunc is not thread-safe; call during initialization
+// RegisterStreamChunkConcatFunc is not thread-safe, needs to be used during initialization phase
RegisterStreamChunkConcatFunc(func(ss []TestStruct)(TestStruct, error){
ret := TestStruct{Body:""}
for i := range ss {
@@ -283,11 +287,11 @@ RegisterStreamChunkConcatFunc(func(ss []TestStruct)(TestStruct, error){
})
```
-Eino provides defaults for `*schema.Message`, `[]*schema.Message`, and `string`.
+Eino provides concat methods for *schema.Message, []*schema.Message and string by default.
-## Interrupt & CheckPoint in Nested Graphs
+## Interrupt&CheckPoint in Nested Graphs
-When the parent sets a `CheckpointStore`, use `WithGraphCompileOptions` during `AddGraphNode` to configure child interrupts:
+With the premise that the parent graph passes in CheckPointer, when using AddGraphNode, passing InterruptNodes through WithGraphCompileOptions can enable the sub-graph's Interrupt&CheckPoint. If the parent graph doesn't have CheckPointer set, an error will be reported during Compile.
```go
/* graph compose code
@@ -300,15 +304,15 @@ g.AddGraphNode("node1", subGraph, WithGraphCompileOptions(
g.Compile(ctx, WithCheckPointStore(cp))
```
-If a child interrupts, resuming modifies the child's state. TODO: clarify Path usage in `StateModifier`.
+If interrupted in a sub-graph, the state modified when resuming should be the sub-graph state. TODO, explain Path usage in StateModifier
-## Recovery
+## Resume
-Recovery: subsequent graph runs after an interrupt and checkpoint save.
+Resume: Graph runs after Interrupt and checkpoint saving.
-### Prior to Eino v0.7.0
+### Before eino v0.7.0
-Modify `State` to affect resume behavior.
+Influence resume behavior by modifying State.
```go
// compose/checkpoint.go
@@ -317,7 +321,7 @@ type StateModifier func(ctx context.Context, path NodePath, state any) error
func WithStateModifier(sm StateModifier) GraphCompileOption
```
-`StateModifier` is applied during graph resume, before execution. `path` applies to nested graphs; for non-nested graphs, it is an empty slice.
+StateModifier takes effect when Graph resumes running, allowing modification of State before running. Path takes effect in nested graphs; treat as empty array for non-nested.
```go
/* graph compose and compile
@@ -339,11 +343,11 @@ _, err = runner.Invoke(ctx, input/*unused*/,
```
> 💡
-> During resume, input is ignored; pass a zero value.
+> Input is not read during Resume, so passing empty input is fine at this time.
-### From Eino v0.7.0 onward
+### In eino v0.7.0 and later
-In addition to `StateModifier`, you can selectively resume particular interrupt points and provide resume data:
+Besides StateModifier, you can also selectively resume a specific interrupt point, and directly pass "resume data" to the specified "interrupt point":
```go
// specifically resume particular interrupt point(s),
@@ -357,17 +361,18 @@ func ResumeWithData(ctx context.Context, interruptID string, data any) context.C
func BatchResumeWithData(ctx context.Context, resumeData map[string]any) context.Context
```
-`InterruptID` is retrieved from the interrupt error:
+Where `InterruptID` is obtained from the interrupt error:
```go
interruptInfo, isInterrupt := ExtractInterruptInfo(err)
if isInterrupt {
- // possibly multiple interrupt points; take the first for illustration
+ // maybe multiple interrupt points exist here,
+ // we only take the first one for illustration purpose
interruptID = interruptInfo.InterruptContexts[0].ID
}
```
-`resumeData` is defined by the interrupt point. For example, a Tool interrupts to request approval and defines `ApprovalResult` as resume data:
+`resumeData` is the type defined by the interrupt point. For example, a Tool interrupted and requires user "approval" to execute this Tool, customizing an `ApprovalResult` as resumeData:
```go
func (i InvokableApprovableTool) InvokableRun(ctx context.Context, argumentsInJSON string,
@@ -388,7 +393,7 @@ func (i InvokableApprovableTool) InvokableRun(ctx context.Context, argumentsInJS
}
isResumeTarget, hasData, data := compose.GetResumeContext[*ApprovalResult](ctx)
- if !isResumeTarget { // interrupted but not explicitly resumed; reinterrupt and wait for approval again
+ if !isResumeTarget { // was interrupted but not explicitly resumed, reinterrupt and wait for approval again
return "", compose.StatefulInterrupt(ctx, &ApprovalInfo{
ToolName: toolInfo.Name,
ArgumentsInJSON: storedArguments,
@@ -413,24 +418,24 @@ func (i InvokableApprovableTool) InvokableRun(ctx context.Context, argumentsInJS
# Examples
-### Prior to Eino v0.7.0
+### Before eino v0.7.0
[https://github.com/cloudwego/eino-examples/tree/main/compose/graph/react_with_interrupt](https://github.com/cloudwego/eino-examples/tree/main/compose/graph/react_with_interrupt)
-### From Eino v0.7.0 onward
+### After eino v0.7.0
[https://github.com/cloudwego/eino/blob/main/compose/resume_test.go](https://github.com/cloudwego/eino/blob/main/compose/resume_test.go)
Including:
-`TestInterruptStateAndResumeForRootGraph`: simple dynamic interrupt
+`TestInterruptStateAndResumeForRootGraph`: Simple dynamic interrupt
-`TestInterruptStateAndResumeForSubGraph`: subgraph interrupt
+`TestInterruptStateAndResumeForSubGraph`: Sub-graph interrupt
-`TestInterruptStateAndResumeForToolInNestedSubGraph`: nested subgraph tool interrupt
+`TestInterruptStateAndResumeForToolInNestedSubGraph`: Tool interrupt inside nested sub-graph
-`TestMultipleInterruptsAndResumes`: parallel interrupts
+`TestMultipleInterruptsAndResumes`: Parallel interrupts
-`TestReentryForResumedTools`: tool interrupt in ReAct Agent, multiple re-entries after resume
+`TestReentryForResumedTools`: Tool interrupt in ReAct Agent, multiple loop executions after resume
-`TestGraphInterruptWithinLambda`: Lambda node contains a standalone Graph and interrupts internally
+`TestGraphInterruptWithinLambda`: Lambda node contains independent Graph with internal interrupt
diff --git a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md
index e7124274fd0..5975d0e8ce5 100644
--- a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md
+++ b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles.md
@@ -1,88 +1,88 @@
---
Description: ""
-date: "2026-01-20"
+date: "2026-03-02"
lastmod: ""
tags: []
title: 'Eino: Orchestration Design Principles'
weight: 2
---
-The mainstream language for large model application orchestration frameworks is Python, a language known for its flexibility. While flexibility facilitates SDK development, it also places a cognitive burden on SDK users.
+The mainstream language for large model application orchestration frameworks is Python, known for its flexibility. While flexibility brings convenience to SDK development, it also adds cognitive burden for SDK users.
-Eino, based on Golang, is `statically typed`, performing type checking at compile time, avoiding the runtime type issues of dynamic languages like Python.
+Eino, based on Golang, is `statically typed`, performing type checking at Compile time, avoiding runtime type issues common in dynamic languages like Python.
-## Upstream-Downstream `Type Alignment` as the Fundamental Principle
+## `Type Alignment` of Upstream and Downstream as the Basic Principle
-Eino's most basic orchestration method is graph, along with the simplified wrapper chain. Regardless of the orchestration method, the essence is `logic nodes` + `upstream/downstream relationships`. When the orchestration product runs, it executes from one logic node, then proceeds to run the next node connected to it.
+Eino's most basic orchestration method is graph, as well as the simplified wrapper chain. Regardless of which orchestration method is used, the essence is `logic nodes` + `upstream-downstream relationships`. When the orchestration product runs, it runs from one logic node, then runs the next node connected to this node in the next step.
-This implies a fundamental assumption: **The output value of the previous running node can be used as the input value of the next node.**
+This implies a basic assumption: **The output value of the previous running node can serve as the input value for the next node.**
In Golang, there are two basic approaches to implement this assumption:
-1. Convert the inputs and outputs of different nodes to a more generalized type, such as `any`, `map[string]any`, etc.
- 1. Adopting the approach of generalizing to any, but the corresponding cost is: developers need to explicitly convert to specific types when writing code. This greatly increases the cognitive burden on developers, so this approach was ultimately abandoned.
- 2. LangChain's approach can be seen as passing `map[string]any` throughout, where each logic node uses the corresponding key to get the corresponding value according to its needs. In the langchaingo implementation, this is exactly how it's done, but similarly, any in Golang still requires `type assertion` to be used. This approach still has a significant cognitive burden for developers.
+1. Convert the input and output of different nodes to a more generalized type, such as `any`, `map[string]any`, etc.
+ 1. Adopting the generalization to any approach, but the corresponding cost is: developers need to explicitly convert to specific types when writing code. This would greatly increase developers' cognitive burden, so this approach was ultimately abandoned.
+ 2. Langchain's approach can be seen as passing `map[string]any` throughout, with each logic node taking the corresponding value using the corresponding key according to its needs. In the langchaingo implementation, it follows this approach, but similarly, any in Golang still requires `type assertion` to be used. This approach still has significant cognitive burden for developers.
2. Keep the input and output types of each node as expected by developers, and ensure upstream and downstream types are consistent during the Compile phase.
-Approach 2 is the final solution chosen by Eino. This approach is the easiest to understand when orchestrating - the whole process is like `building blocks`, where each block's protruding and recessed parts have their own specifications, and only matching specifications can form upstream/downstream relationships.
+Approach 2 is the final approach chosen by Eino. This approach is the easiest to understand during orchestration. The entire process is like `building blocks`, where each block's protruding and recessed parts have their own specifications, and only when specifications match can they form upstream-downstream relationships.
-As shown below:
+As shown in the figure below:
-For any orchestration, only when the downstream can recognize and process the upstream's output can the orchestration run normally. This fundamental assumption is clearly expressed in Eino, allowing developers to have full confidence in understanding how the orchestration logic runs and flows, rather than guessing whether the values passed from a series of any are correct.
+For orchestration, only when downstream can recognize and process upstream output can the orchestration run normally. This basic assumption is clearly expressed in Eino, allowing developers to have full confidence in understanding how the orchestration logic runs and flows when using Eino for orchestration, rather than guessing whether the passed value is correct from a series of any.
### Type Alignment in Graph
#### Edge
-In a graph, a node's output flows to the next node along an `edge`, therefore, nodes connected by edges must be type-aligned.
+In a graph, a node's output flows to the next node along `edges`, so nodes connected by edges must have type alignment.
-As shown below:
+As shown in the figure below:
-> This simulates a scenario of ① direct conversation with a large model ② using RAG mode, with results that can be used to compare the effects of both modes
+> This simulates scenarios of ① direct conversation with large model ② using RAG mode, with results available for comparing the effects of both modes
-The green parts in the diagram are normal Edge connections, which require that the upstream output must be `assignable` to the downstream. The acceptable types are:
+The green parts in the figure are normal Edge connections, requiring that upstream output can be `assigned` to downstream. Acceptable types include:
-① Same upstream and downstream types: e.g., upstream outputs *schema.Message and downstream input is also *schema.Message
+① Same upstream and downstream types: e.g., upstream outputs *schema.Message and downstream inputs also *schema.Message
-② Downstream receives an interface, upstream implements that interface: e.g., upstream struct implements the Format() interface, downstream receives an interface{ Format() }. A special case is when downstream is any (empty interface), upstream always implements any, so it can always connect.
+② Downstream accepts interface, upstream implements that interface: e.g., upstream struct implements Format() interface, downstream accepts interface{ Format() }. A special case is when downstream is any (empty interface), upstream always implements any, so connection is always possible.
-③ Upstream is an interface, downstream is a concrete type: When the downstream concrete type implements the upstream interface type, it may or may not work - this cannot be determined at compile time, only at runtime when the upstream's concrete type is determined. For detailed description, see: [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles)
+③ Upstream is interface, downstream is concrete type: When the downstream concrete type implements the upstream interface type, it may or may not work, cannot be determined at compile time, only at runtime when the upstream's concrete type is determined. For detailed description, see: [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles)
-The yellow parts in the diagram show another type conversion mechanism provided by Eino: if the downstream receives type `map[string]any`, but the upstream output type is not map[string]any, you can use `graph.AddXXXNode(node_key, xxx, compose.WithOutputKey("outkey")` to convert the upstream output type to map[string]any, where the map's key is the OutputKey specified in the option. This mechanism is convenient when multiple edges converge to a single node.
+The yellow parts in the figure are another type conversion mechanism provided by Eino: if downstream accepts type `map[string]any`, but upstream output type is not map[string]any, you can use `graph.AddXXXNode(node_key, xxx, compose.WithOutputKey("outkey")` to convert upstream output type to map[string]any, where the map key is the OutputKey specified in the option. This mechanism is convenient when multiple edges converge to a node.
-Similarly, if the upstream is `map[string]any`, but the downstream input type is not map[string]any, you can use `graph.AddXXXNode(node_key, xxx, compose.WithInputKey("inkey")` to get one key's value from the upstream output as the downstream's input.
+Similarly, if upstream is `map[string]any`, but downstream input type is not map[string]any, you can use `graph.AddXXXNode(node_key, xxx, compose.WithInputKey("inkey")` to get one key's value from upstream output as downstream input.
#### Branch
-If a node is followed by multiple edges, each edge's downstream node will run once. Branch is another mechanism: a branch is followed by n nodes, but only the node corresponding to the node key returned by the condition will run. Nodes after the same branch must be type-aligned.
+If a node is followed by multiple edges, all downstream nodes of each edge will run once. Branch is another mechanism: a branch has n nodes following it, but only the node corresponding to the node key returned by condition will run. All nodes following the same branch must have type alignment.
-As shown below:
+As shown in the figure below:
> This simulates the running logic of a react agent
-As you can see, a branch itself has a `condition`, and this function's input must be type-aligned with the upstream. At the same time, each node connected after a branch must also, like the condition, be able to receive the upstream's output.
+As you can see, a branch itself has a `condition`, whose input must align with upstream type. Also, each node following a branch must, like condition, be able to receive upstream output.
### Type Alignment in Chain
#### Chain
-From an abstract perspective, a chain is a `chain`, as shown below:
+From an abstract perspective, chain is a `chain`, as shown below:
Logic node types can be divided into 3 categories:
-- Orchestrable components (e.g., chat model, chat template, retriever, lambda, graph, etc.)
+- Orchestratable components (such as chat model, chat template, retriever, lambda, graph, etc.)
- Branch nodes
- Parallel nodes
-As you can see, from the chain's perspective, whether it's a simple node (e.g., chat model) or a complex node (e.g., graph, branch, parallel), they are all the same - during execution, one step is one node's execution.
+As you can see, from the chain's perspective, whether it's a simple node (e.g., chat model) or a complex node (e.g., graph, branch, parallel), they're all the same. During execution, one step is the running of one node.
Therefore, between upstream and downstream nodes in a chain, types must be aligned, as follows:
@@ -106,25 +106,25 @@ func TestChain() {
}
```
-The above logic represented as a diagram:
+The logic above represented in a figure is as follows:
-If upstream and downstream types are not aligned, chain will return an error at chain.Compile(). Graph will report an error at graph.AddXXXNode().
+If upstream and downstream types don't align, chain will return an error at chain.Compile(). Graph will report an error at graph.AddXXXNode().
#### Parallel
-Parallel is a special type of node in chain. From the chain's perspective, parallel is no different from other nodes. Inside parallel, its basic topology structure is as follows:
+Parallel is a special type of node in chain. From the chain's perspective, parallel is no different from other nodes. Internally, parallel's basic topology is as follows:
-One of the structures formed by multiple edges in a graph is this. The basic assumption here is: each edge in a parallel has exactly one node. Of course, this one node can also be a graph. Note that currently the framework does not directly provide the ability to nest branch or parallel within parallel.
+One of the structures formed by multiple edges in graph is this. The basic assumption here is: each edge in a parallel has one and only one node. Of course, this one node can also be a graph. But note that currently the framework doesn't directly provide the ability to nest branch or parallel within parallel.
-For each node in parallel, since their upstream node is the same, they all need to be type-aligned with the upstream node's output type. For example, in the diagram, the upstream node outputs `*schema.Message`, so each node must be able to receive this type. The receiving methods are the same as in graph, typically using `same type`, `interface definition`, `any`, or `input key option`.
+In parallel, since each node has the same upstream node, they all need to align with the upstream node's output type. For example, if the upstream node in the figure outputs `*schema.Message`, each node must be able to receive this type. The receiving methods are the same as in graph, typically using `same type`, `interface definition`, `any`, or `input key option`.
-The output of a parallel node is always a `map[string]any`, where the key is the output_key specified in `parallel.AddXXX(output_key, xxx, opts...)`, and the value is the actual output of the internal node.
+The output of a parallel node is always a `map[string]any`, where the key is the output_key specified in `parallel.AddXXX(output_key, xxx, opts...)`, and value is the actual output of the internal node.
-An example of building a parallel:
+An example of building a parallel is as follows:
```go
func TestParallel() {
@@ -148,34 +148,34 @@ func TestParallel() {
}
```
-A parallel's view in a chain is as follows:
+A parallel's perspective within a chain is as follows:
-> The diagram simulates the same question being answered by different large models, with results that can be used to compare effects
+> The figure simulates the same question answered by different large models, with results available for comparing effects
-> Note that this structure is only a logical view. Since chain itself is implemented using graph, parallel will be flattened into the underlying graph.
+> Note that this structure is only a logical perspective. Since chain itself is implemented using graph, parallel will be flattened into the graph at the underlying level.
#### Branch
-Chain's branch is similar to graph's branch - all nodes in the branch must be type-aligned with the upstream node, so we won't elaborate here. The special thing about chain branch is that all possible branch nodes will connect to the same node in the chain, or all will connect to END.
+Chain branch is similar to branch in graph. All nodes in the branch must align with the upstream node's type, so no further elaboration here. The special thing about chain branch is that all possible branch nodes will connect to the same node in the chain, or all will connect to END.
### Type Alignment in Workflow
The dimension of type alignment in Workflow changes from overall Input & Output to field level. Specifically:
-- The overall upstream output is type-aligned to a specific field of the downstream.
-- A specific field of the upstream output is type-aligned to the overall downstream.
-- A specific field of the upstream output is type-aligned to a specific field of the downstream input.
+- The entire upstream output aligns to a specific field of downstream.
+- A specific upstream output field aligns to the entire downstream.
+- A specific upstream output field aligns to a specific downstream input field.
The principles and rules are the same as overall type alignment.
### Type Alignment of StateHandler
-StatePreHandler: The input type needs to align with the corresponding node's non-streaming input type.
+StatePreHandler: Input type needs to align with the corresponding node's non-streaming input type.
```go
-// input type is []*schema.Message, aligns with ChatModel's non-streaming input type
+// input type is []*schema.Message, aligning with ChatModel's non-streaming input type
preHandler := func(ctx context.Context, input []*schema.Message, state *state) ([]*schema.Message, error) {
// your handler logic
}
@@ -183,10 +183,10 @@ preHandler := func(ctx context.Context, input []*schema.Message, state *state) (
AddChatModelNode("xxx", model, WithStatePreHandler(preHandler))
```
-StatePostHandler: The input type needs to align with the corresponding node's non-streaming output type.
+StatePostHandler: Input type needs to align with the corresponding node's non-streaming output type.
```go
-// input type is *schema.Message, aligns with ChatModel's non-streaming output type
+// input type is *schema.Message, aligning with ChatModel's non-streaming output type
postHandler := func(ctx context.Context, input *schema.Message, state *state) (*schema.Message, error) {
// your handler logic
}
@@ -194,10 +194,10 @@ postHandler := func(ctx context.Context, input *schema.Message, state *state) (*
AddChatModelNode("xxx", model, WithStatePostHandler(postHandler))
```
-StreamStatePreHandler: The input type needs to align with the corresponding node's streaming input type.
+StreamStatePreHandler: Input type needs to align with the corresponding node's streaming input type.
```go
-// input type is *schema.StreamReader[[]*schema.Message], aligns with ChatModel's streaming input type
+// input type is *schema.StreamReader[[]*schema.Message], aligning with ChatModel's streaming input type
preHandler := func(ctx context.Context, input *schema.StreamReader[[]*schema.Message], state *state) (*schema.StreamReader[[]*schema.Message], error) {
// your handler logic
}
@@ -205,10 +205,10 @@ preHandler := func(ctx context.Context, input *schema.StreamReader[[]*schema.Mes
AddChatModelNode("xxx", model, WithStreamStatePreHandler(preHandler))
```
-StreamStatePostHandler: The input type needs to align with the corresponding node's streaming output type.
+StreamStatePostHandler: Input type needs to align with the corresponding node's streaming output type.
```go
-// input type is *schema.StreamReader[*schema.Message], aligns with ChatModel's streaming output type
+// input type is *schema.StreamReader[*schema.Message], aligning with ChatModel's streaming output type
postHandler := func(ctx context.Context, input *schema.StreamReader[*schema.Message], state *state) (*schema.StreamReader[*schema.Message], error) {
// your handler logic
}
@@ -216,15 +216,15 @@ postHandler := func(ctx context.Context, input *schema.StreamReader[*schema.Mess
AddChatModelNode("xxx", model, WithStreamStatePostHandler(postHandler))
```
-### Type Alignment in Invoke and Stream Modes
+### Type Alignment Methods Under Invoke and Stream
-In Eino, the result of orchestration is a graph or chain. To run it, you need to use `Compile()` to generate a `Runnable` interface.
+In Eino, orchestration results are graph or chain. To run, you need to use `Compile()` to generate a `Runnable` interface.
-An important function of Runnable is to provide four calling methods: "Invoke", "Stream", "Collect", and "Transform".
+An important function of Runnable is providing four calling methods: "Invoke", "Stream", "Collect", and "Transform".
-> For an introduction to the above calling methods and detailed Runnable introduction, see: [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
+> For introduction of the above calling methods and detailed Runnable introduction, see: [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials)
-Suppose we have a `Graph[[]*schema.Message, []*schema.Message]` with a ChatModel node and a Lambda node. After Compile, it becomes a `Runnable[[]*schema.Message, []*schema.Message]`.
+Suppose we have a `Graph[[]*schema.Message, []*schema.Message]` with a ChatModel node and a Lambda node. After Compile, it's a `Runnable[[]*schema.Message, []*schema.Message]`.
```go
package main
@@ -281,22 +281,22 @@ func TestTypeMatch(t *testing.T) {
}
```
-When we call the compiled Runnable above in Stream mode, the model node will output `*schema.StreamReader[*Message]`, but the lambda node is an InvokableLambda that only accepts non-streaming `*schema.Message` as input. This also conforms to the type alignment rules because the Eino framework will automatically concatenate the streamed Message into a complete Message.
+When we call the compiled Runnable with Stream, the model node outputs `*schema.StreamReader[*Message]`, but the lambda node is an InvokableLambda that only accepts non-streaming `*schema.Message` as input. This also conforms to type alignment rules because the Eino framework automatically concatenates streaming Messages into complete Messages.
-In stream mode, concatenating frames is a very common operation. During concatenation, all elements from `*StreamReader[T]` are first extracted and converted to `[]T`, then an attempt is made to concatenate `[]T` into a complete `T`. The framework has built-in support for concatenating the following types:
+In stream mode, concatenating frames is a very common operation. When concatenating, all elements in `*StreamReader[T]` are first extracted and converted to `[]T`, then attempt to concatenate `[]T` into a complete `T`. The framework has built-in support for concatenating the following types:
-- `*schema.Message`: See `schema.ConcatMessages()`
-- `string`: Implementation logic is equivalent to `+=`
-- `[]*schema.Message`: See `compose.concatMessageArray()`
-- `Map`: Merge values with the same key, with the same merge logic as above. If there are types that cannot be merged, it fails (note: not overwrite)
-- Other slices: Can only be merged when the slice has exactly one non-zero element.
+- `*schema.Message`: Details in `schema.ConcatMessages()`
+- `string`: Implementation logic equivalent to `+=`
+- `[]*schema.Message`: Details in `compose.concatMessageArray()`
+- `Map`: Merge values of the same key, merge logic same as above. If there are types that cannot be merged, it fails (ps: not overwrite)
+- Other slices: Can only be merged when only one element in the slice is non-zero value.
For other scenarios, or when users want to override the default behavior above with custom logic, developers can implement their own concat method and register it to the global concatenation function using `compose.RegisterStreamChunkConcatFunc()`.
-Example:
+Example as follows:
```go
-// Assume our own struct is as follows
+// Suppose our own struct is as follows
type tStreamConcatItemForTest struct {
s string
}
@@ -312,41 +312,41 @@ func concatTStreamForTest(items []*tStreamConcatItemForTest) (*tStreamConcatItem
}
func Init() {
- // Register to the global concatenation method
+ // Register to global concatenation method
compose.RegisterStreamChunkConcatFunc(concatTStreamForTest)
}
```
### Scenarios Where Type Alignment is Checked at Runtime
-Eino's Graph type alignment check is performed at `err = graph.AddEdge("node1", "node2")` to check whether the two node types match. This allows type mismatch errors to be discovered during `the graph building process` or `the Compile process`. This applies to rules ① ② ③ listed in [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles).
+Eino's Graph type alignment check occurs at `err = graph.AddEdge("node1", "node2")` when checking whether the two nodes' types match. This allows discovering type mismatch errors during `graph building` or `Compile process`, applicable to rules ① ② ③ listed in [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles).
-When the upstream node's output is an `interface`, if the downstream node type implements that `interface`, the upstream may be able to convert to the downstream type (type assertion), but this can only be known during `runtime`. The type check for this scenario is moved to runtime.
+When the upstream node's output is `interface`, if the downstream node type implements that `interface`, upstream may be convertible to downstream type (type assertion), but can only be determined during `runtime`. At this point, if it's determined that upstream cannot be assigned to downstream, an error will be thrown.
-The structure is shown below:
+The structure is shown in the figure below:
-This scenario is suitable for cases where developers can handle upstream and downstream type alignment themselves, and can choose downstream execution nodes based on different types.
+This scenario is suitable for cases where developers can handle upstream-downstream type alignment themselves, selecting downstream execution nodes based on different types.
## Design Choices with Clear Preferences
-### External Variables Read-Only Principle
+### Read-Only Principle for External Variables
-When data flows between Nodes, Branches, and Handlers in Eino's Graph, it is always variable assignment, not Copy. When Input is a reference type, such as Struct pointer, map, or slice, modifications to Input inside Node, Branch, or Handler will have side effects externally and may cause concurrency issues. Therefore, Eino follows the external variables read-only principle: Node, Branch, Handler should not modify Input internally. If modification is needed, copy it first.
+When data flows between Nodes, Branches, and Handlers in Eino's Graph, it's all variable assignment, not Copy. When Input is a reference type, such as Struct pointer, map, slice, modifications to Input inside Node, Branch, or Handler will have side effects externally, potentially causing concurrency issues. Therefore, Eino follows the read-only principle for external variables: Node, Branch, and Handler should not modify Input internally. If modification is needed, first Copy it yourself.
This principle also applies to Chunks in StreamReader.
-### Fan-in and Merge
+### Fan-In and Merge
-**Fan-in**: Data from multiple upstreams flows into the downstream, together serving as the downstream's input. It is necessary to clearly define how multiple upstream outputs are **merged**.
+**Fan-In**: Data from multiple upstreams flows into downstream, together serving as downstream input. Need to clearly define how outputs from multiple upstreams are **merged**.
-By default, first, the **actual types** of multiple upstream outputs must be the same and be a Map, and keys must not overlap. Second:
+By default, first, the **actual types** of multiple upstream outputs must be the same and be Map, and keys must not overlap between them. Then:
-- In non-streaming scenarios, after merging, it becomes one Map containing all key-value pairs from all upstreams.
+- In non-streaming scenarios, after merging becomes one Map, containing all key-value pairs from all upstreams.
- In streaming scenarios, multiple upstream StreamReaders of the same type are merged into one StreamReader. The actual Recv effect is fair reading from multiple upstream StreamReaders.
-When AddNode, you can add the WithOutputKey Option to convert the node's output to a Map:
+When AddNode, you can convert node output to Map by adding the WithOutputKey Option:
```go
// This node's output will change from string to map[string]any,
@@ -357,121 +357,121 @@ graph.AddLambdaNode("your_node_key", compose.InvokableLambda(func(ctx context.Co
}), compose.WithOutputKey("your_output_key"))
```
-You can also register a Merge method to implement merge of any type:
+You can also implement merge of any type by registering a Merge method:
```go
// eino/compose/values_merge.go
func RegisterValuesMergeFunc[T any](fn func([]T) (T, error))
```
-Workflow can map multiple output fields from multiple upstreams to different fields of the downstream node. This is not a merge scenario, but point-to-point field mapping. In fact, eino workflow currently does not support "multiple upstream fields mapping to the same downstream field simultaneously".
+Workflow can map multiple output fields from multiple upstreams to different fields of the downstream node. This doesn't belong to merge scenarios, but point-to-point field mapping. In fact, eino workflow currently doesn't support "multiple upstream fields mapping to the same downstream field at the same time".
-### Streaming Handling
+### Stream Processing
-Eino believes that components should only need to implement the streaming paradigms that are real in business scenarios. For example, ChatModel doesn't need to implement Collect. Therefore, in orchestration scenarios, Eino automatically helps all nodes **complete missing streaming paradigms**.
+Eino believes that components should only need to implement streaming paradigms that are real in business scenarios, for example, ChatModel doesn't need to implement Collect. Therefore, in orchestration scenarios, Eino automatically helps all nodes **complete missing streaming paradigms**.
-Running Graph in Invoke mode, all internal nodes run in Invoke paradigm. Running Graph in Stream, Collect, or Transform mode, all internal nodes run in Transform paradigm.
+Running Graph with Invoke, internal nodes all run with Invoke paradigm. Running Graph with Stream, Collect or Transform, internal nodes all run with Transform paradigm.
-**Auto Concatenate**: For scenarios where Stream chunks are concatenated into complete content, user-registered custom concatenation functions are used first, followed by framework-provided default behaviors, including Message, Message array, String, Map, and Struct and Struct pointers.
+**Auto Concatenate**: For scenarios where Stream chunks are concatenated into complete content, first use user-registered custom concatenation functions, then execute framework-provided default behavior, including Message, Message array, String, Map, and Struct and Struct pointers.
-**Auto Box**: For scenarios where non-streaming T needs to become StreamReader[T], the framework executes automatically.
+**Auto Box**: For scenarios where non-streaming T needs to become StreamReader[T], the framework automatically executes.
-**Auto Merge**: See the "Fan-in and Merge" section above.
+**Auto Merge**: See "Fan-In and Merge" section above.
-**Auto Copy**: Automatic stream copying in scenarios that require it, including a stream fanning out to multiple downstream nodes, and a stream entering one or more callback handlers.
+**Auto Copy**: Automatic stream copying in scenarios requiring stream copying, including one stream fanning out to multiple downstream nodes, one stream entering one or more callback handlers.
-Finally, Eino requires all orchestration elements to be able to sense and handle streams. This includes branch, state handler, callback handler, passthrough, lambda, etc.
+Finally, Eino requires all orchestration elements to be able to sense and process streams. Including branch, state handler, callback handler, passthrough, lambda, etc.
-For Eino's stream handling capabilities, see [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials).
+For detailed Eino stream handling capabilities, see [Eino Stream Programming Essentials](/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials).
### Global State
-**State**: Pass the State creation method through `compose.WithGenLocalState` when NewGraph. This request-scoped global state can be read and written in various stages of a request.
+**State**: Pass the State creation method through `compose.WithGenLocalState` when NewGraph. This request-level global state can be read and written throughout various stages of a request.
-Eino recommends using `StatePreHandler` and `StatePostHandler`, with the functional positioning of:
+Eino recommends using `StatePreHandler` and `StatePostHandler`, with functionality positioned as:
-- StatePreHandler: Read and write State before each node execution, and replace the node's Input as needed. Input needs to align with the node's non-streaming input type.
-- StatePostHandler: Read and write State after each node execution, and replace the node's Output as needed. Input needs to align with the node's non-streaming output type.
+- StatePreHandler: Read and write State before each node executes, and replace node Input as needed. Input needs to align with node's non-streaming input type.
+- StatePostHandler: Read and write State after each node executes, and replace node Output as needed. Input needs to align with node's non-streaming output type.
-For streaming scenarios, use the corresponding `StreamStatePreHandler` and `StreamStatePostHandler`, with input needing to align with the node's streaming input and streaming output types respectively.
+For streaming scenarios, use the corresponding `StreamStatePreHandler` and `StreamStatePostHandler`, with input needing to align with node's streaming input and streaming output types respectively.
-These state handlers are located outside the node, affecting the node through modifications to Input or Output, thus ensuring the node's "state-independent" characteristic.
+These state handlers are located outside the node, affecting the node through modifications to Input or Output, thus ensuring the node's "state-agnostic" characteristic.
-If you need to read and write State inside a node, Eino provides the `ProcessState[S any](ctx context.Context, handler func(context.Context, S) error) error` function.
+If State needs to be read and written inside the node, Eino provides the `ProcessState[S any](ctx context.Context, handler func(context.Context, S) error) error` function.
-The Eino framework will lock at all positions where State is read or written.
+Eino framework will lock at all positions where State is read or written.
### Callback Injection
The Eino orchestration framework believes that components entering orchestration may or may not have Callback aspects embedded internally. This information is determined by whether the component implements the `Checker` interface and the return value of the `IsCallbacksEnabled` method in the interface.
- When `IsCallbacksEnabled` returns true, the Eino orchestration framework uses the Callback aspects inside the component implementation.
-- Otherwise, it automatically wraps Callback aspects outside the component implementation, (only) reporting input and output.
+- Otherwise, automatically wrap Callback aspects outside the component implementation, (can only) report input and output.
-In either case, RunInfo will be automatically inferred.
+Either way, RunInfo will be automatically inferred.
-At the same time, for the Graph as a whole, Callback aspects will always be injected, with RunInfo being the Graph itself.
+Also, for the Graph as a whole, Callback aspects will definitely be injected, with RunInfo being the Graph itself.
-For the complete description of Eino's Callback capabilities, see [Eino: Callback User Manual](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual).
+For complete description of Eino's Callback capability, see [Eino: Callback User Manual](/docs/eino/core_modules/chain_and_graph_orchestration/callback_manual).
### Option Distribution
Eino supports various dimensions of Call Option distribution:
-- Global by default, i.e., distributed to all nodes, including nested internal graphs.
-- Can add Options for a specific component type, which are then distributed to all nodes of that type by default, such as AddChatModelOption. Lambda that defines its own Option type can also specify Options to itself this way.
+- Default global, i.e., distributed to all nodes, including nested internal graphs.
+- Can add Options for a specific component type, which by default distributes to all nodes of that type, such as AddChatModelOption. Lambda with defined unique Option types can also specify Options to itself this way.
- Can specify any specific nodes using `DesignateNode(key ...string)`.
-- Can specify nested graphs at any depth, or any specific nodes within them, using `DesignateNodeWithPath(path ...*NodePath)`.
+- Can specify nested graphs of any depth, or any specific nodes within them, using `DesignateNodeWithPath(path ...*NodePath)`.
-For the complete description of Eino's Call Option capabilities, see [Eino: CallOption Capabilities and Specifications](/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities).
+For complete description of Eino's Call Option capability, see [Eino: CallOption Capabilities and Specifications](/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities).
### Graph Nesting
-The graph orchestration product `Runnable` has a very similar interface form to Lambda. Therefore, a compiled graph can be simply wrapped as a Lambda and nested into other graphs as a Lambda node.
+Graph orchestration product `Runnable` has a very similar interface form to Lambda. Therefore, a compiled graph can be simply wrapped as Lambda and nested into other graphs as a Lambda node.
-Another way is to directly nest Graph, Chain, Workflow, etc. into other graphs through AddGraph before compilation. The differences between the two approaches are:
+Another way, before compilation, Graph, Chain, Workflow, etc. can all be directly nested into other graphs through AddGraph. The differences between the two methods are:
-- With the Lambda approach, there will be an extra Lambda node level in the trace. Other Callback handler perspectives will also see an extra layer.
-- With the Lambda approach, CallOption needs to be received through Lambda's Option, and cannot use DesignateNodeWithPath.
-- With the Lambda approach, the internal graph needs to be compiled beforehand. With direct AddGraph, the internal graph is compiled together with the upper-level graph.
+- Lambda method, there will be an extra Lambda node level in trace. Other Callback handler perspectives will also see an extra layer.
+- Lambda method, needs to use Lambda's Option to accept CallOption, cannot use DesignateNodeWithPath.
+- Lambda method, internal graph needs to be compiled first. AddGraph directly, internal graph compiles together with parent graph.
## Internal Mechanisms
### Execution Sequence
-Taking an InvokableLambda (input is string, output is int) with StatePreHandler, StatePostHandler, InputKey, OutputKey, and no internal Callback aspects as an example, the complete streaming execution sequence in the graph is as follows:
+Taking an InvokableLambda (input is string, output is int) with StatePreHandler, StatePostHandler, InputKey, OutputKey added, and no Callback aspects implemented internally as an example, the complete streaming execution sequence in the graph is as follows:
In workflow scenarios, field mapping occurs at two positions:
-- After the node execution's StatePostHandler and "stream copy" steps, the fields needed by each downstream are extracted separately.
-- After the "merge" step before node execution and before StatePreHandler, the extracted upstream field values are converted to the current node's input.
+- After StatePostHandler and "stream copy" step after node execution, fields needed by each downstream are extracted separately.
+- After "merge" step before node execution, before StatePreHandler, extracted upstream field values are converted to current node's input.
### Execution Engine
-When `NodeTriggerMode == AnyPredecessor`, the graph executes with the pregel engine, corresponding to a directed cyclic graph topology. The characteristics are:
+When `NodeTriggerMode == AnyPredecessor`, graph executes with pregel engine, corresponding topological structure is directed cyclic graph. Characteristics are:
-- All successor nodes of the currently executing one or more nodes form a SuperStep and execute together. At this point, these new nodes become the "current" nodes.
-- Supports Branch, supports cycles in the graph, but may require manually adding passthrough nodes to ensure the nodes in the SuperStep meet expectations, as shown below:
+- All successor nodes of currently executing one or more nodes, as a SuperStep, execute together. At this point, these new nodes become "current" nodes.
+- Supports Branch, supports cycles in graph, but may need to manually add passthrough nodes to ensure nodes in SuperStep meet expectations, as shown:
-In the above diagram, Node 4 and Node 5 are placed together for execution according to the rules, which is probably not as expected. It needs to be changed to:
+In the figure above, Node 4 and Node 5 are placed together for execution by rule, which probably doesn't meet expectations. Need to change to:
-When `NodeTriggerMode == AllPredecessor`, the graph executes with the dag engine, corresponding to a directed acyclic graph topology. The characteristics are:
+When `NodeTriggerMode == AllPredecessor`, graph executes with dag engine, corresponding topological structure is directed acyclic graph. Characteristics are:
-- Each node has definite predecessor nodes, and this node only has the condition to run after all predecessor nodes are complete.
-- Does not support cycles in the graph, because it would break the assumption that "each node has definite predecessor nodes".
-- Supports Branch. At runtime, nodes not selected by the Branch are marked as skipped, not affecting the AllPredecessor semantics.
+- Each node has definite predecessor nodes. Only when all predecessor nodes complete does this node have running conditions.
+- Doesn't support cycles in graph, because it would break the assumption that "each node has definite predecessor nodes".
+- Supports Branch. At runtime, nodes not selected by Branch are marked as skipped, not affecting AllPredecessor semantics.
> 💡
-> After setting NodeTriggerMode = AllPredecessor, nodes will execute after all predecessors are ready, but not immediately - they still follow SuperStep semantics, running new runnable nodes after a batch of nodes has completed execution.
+> After setting NodeTriggerMode = AllPredecessor, nodes execute after all predecessors are ready, but not immediately. They still follow SuperStep—running new runnable nodes after a batch of nodes all complete execution.
>
> If you pass compose.WithEagerExecution() during Compile, ready nodes will run immediately.
>
> In Eino v0.4.0 and later versions, setting NodeTriggerMode = AllPredecessor will enable EagerExecution by default.
-In summary, pregel mode is flexible and powerful but has additional cognitive burden, while dag mode is clear and simple but has limited scenarios. In the Eino framework, Chain uses pregel mode, Workflow uses dag mode, and Graph supports both, allowing users to choose between pregel and dag.
+In summary, pregel mode is flexible and powerful but has additional cognitive burden, dag mode is clear and simple but limited in scenarios. In the Eino framework, Chain is pregel mode, Workflow is dag mode, Graph supports both, allowing users to choose from pregel and dag.
diff --git a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md
index 7dcbe2395c1..c90c4e2fcea 100644
--- a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md
+++ b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/stream_programming_essentials.md
@@ -3,49 +3,49 @@ Description: ""
date: "2026-01-30"
lastmod: ""
tags: []
-title: Eino Streaming Essentials
+title: Eino Stream Programming Essentials
weight: 4
---
> 💡
-> Recommended reading first: [Eino: Overview](/docs/eino/overview) and [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles)
+> Recommended reading first: [Eino: Basic Concepts Introduction](/docs/eino/overview) [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles)
-## Streaming in Orchestration: Overview
+## Overview of Orchestration Streaming
-Key factors when orchestrating streaming graphs:
+When orchestrating streaming Graphs, there are several key elements to consider:
-- Which Lambda operators does the component/Lambda offer: choose among Invoke, Stream, Collect, Transform.
-- In the topology, do upstream outputs and downstream inputs match (both streaming or both non-streaming)?
-- If they don’t match, use streaming helpers:
- - Streaming: wrap `T` into a single-chunk `Stream[T]`.
- - Concat: merge `Stream[T]` into a complete `T`. Each frame in `Stream[T]` is a piece of the final `T`.
+- What Lambda operators are included in components/Lambda: Choose from Invoke, Stream, Collect, Transform
+- In the orchestration topology, whether upstream and downstream nodes' inputs and outputs are both streams or both non-streams.
+- If upstream and downstream nodes' stream types cannot match, two operations are needed:
+ - Streaming: Convert T into a single-Chunk Stream[T]
+ - Concat: Merge Stream[T] into a complete T. Each "frame" in Stream[T] is part of this complete T.
-## Semantics of Streaming in Eino
+## The Meaning of Eino Stream Programming
-- Some components naturally emit frames — partial outputs of the final result — i.e., streaming output. Downstream must concat frames into a complete output. A typical example is an LLM.
-- Some components naturally accept frames and can begin meaningful processing before receiving the full input. For example, in a ReAct agent, a branch may decide to call a tool or end execution by inspecting the first frame of the ChatModel’s output.
-- Thus, each component may accept non-streaming or streaming input, and produce non-streaming or streaming output.
-- Combined, there are four streaming paradigms:
+- Some components naturally support outputting in "frames", outputting part of a complete output each time, i.e., "streaming" output. After streaming output is complete, downstream needs to concatenate these "frames" into complete output. A typical example is LLM.
+- Some components naturally support inputting in "frames", starting meaningful business processing or even completing business processing when receiving incomplete input. For example, in a react agent, the branch used to determine whether to call a tool or end the run can make a decision from the first frame of the LLM's streaming output by checking whether the message contains a tool call.
+- Therefore, a component, from the input perspective, has "non-streaming" input and "streaming" input two types. From the output perspective, has "non-streaming" output and "streaming" output two types.
+- Combined, there are four possible streaming programming paradigms
-| Function | Pattern | Interaction | Lambda Constructor | Notes |
-| Invoke | Non-streaming in, non-streaming out | Ping-Pong | compose.InvokableLambda() | |
-| Stream | Non-streaming in, streaming out | Server-Streaming | compose.StreamableLambda() | |
-| Collect | Streaming in, non-streaming out | Client-Streaming | compose.CollectableLambda() | |
-| Transform | Streaming in, streaming out | Bidirectional-Streaming | compose.TransformableLambda() | |
+| Function Name | Mode Description | Interaction Mode Name | Lambda Constructor | Notes |
+| Invoke | Non-streaming input, non-streaming output | Ping-Pong Mode | compose.InvokableLambda() | |
+| Stream | Non-streaming input, streaming output | Server-Streaming Mode | compose.StreamableLambda() | |
+| Collect | Streaming input, non-streaming output | Client-Streaming | compose.CollectableLambda() | |
+| Transform | Streaming input, streaming output | Bidirectional-Streaming | compose.TransformableLambda() | |
-## Streaming at the Single-Component Level
+## Streaming from Single Component Perspective
-Eino is a "component-first" framework; components can be used independently. Do you need to consider streaming when defining component interfaces? The simple answer is no. The complex answer is "follow real business scenarios".
+Eino is a "component first" framework where components can be used independently. When defining component interfaces, do you need to consider streaming programming issues? The simple answer is no. The complex answer is "based on real business scenarios".
-### Business Semantics of Components
+### Component's Own Business Paradigm
-A typical component, such as ChatModel or Retriever, should define its interface according to actual business semantics. If it actually supports a certain streaming paradigm, implement that paradigm. If a certain streaming paradigm has no real business scenario, there's no need to implement it. For example:
+A typical component, like Chat Model or Retriever, just needs to define interfaces based on actual business semantics. If it actually supports a certain streaming paradigm, implement that streaming paradigm. If a certain streaming paradigm has no real business scenario, then no need to implement it. For example:
-- ChatModel: besides `Invoke` (non-streaming), it naturally supports `Stream` (streaming). It therefore implements `Generate` and `Stream`, but `Collect` and `Transform` have no corresponding real business scenarios, so they are not implemented:
+- Chat Model, besides the non-streaming Invoke paradigm, also naturally supports the Stream streaming paradigm, so Chat Model's interface implements both Generate and Stream interfaces. But Collect and Transform have no corresponding real business scenarios, so corresponding interfaces are not implemented:
```go
type ChatModel interface {
@@ -56,7 +56,7 @@ type ChatModel interface {
}
```
-- Retriever: besides `Invoke` (non-streaming), the other three streaming paradigms have no real business scenarios, so it only implements `Retrieve`:
+- Retriever, besides the non-streaming Invoke paradigm, none of the other three streaming paradigms have real business scenarios, so only the Retrieve interface is implemented:
```go
type Retriever interface {
@@ -64,12 +64,12 @@ type Retriever interface {
}
```
-### Paradigms Supported by Components
+### Specific Paradigms Supported by Components
-| Component | Invoke | Stream | Collect | Transform |
-| ChatModel | yes | yes | no | no |
-| ChatTemplate | yes | no | no | no |
+| Component Name | Implements Invoke | Implements Stream | Implements Collect | Implements Transform |
+| Chat model | yes | yes | no | no |
+| Chat template | yes | no | no | no |
| Retriever | yes | no | no | no |
| Indexer | yes | no | no | no |
| Embedder | yes | no | no | no |
@@ -78,36 +78,36 @@ type Retriever interface {
| Tool | yes | yes | no | no |
-Among official Eino components, only ChatModel and Tool additionally support `Stream`; all other components only support `Invoke`. For component details, see: [[Updating] Eino: Components Abstraction & Implementation](/docs/eino/core_modules/components)
+Among Eino official components, besides Chat Model and Tool which additionally support stream, all other components only support invoke. For specific component introduction, see: [[Updating] Eino: Components Abstraction & Implementation](/docs/eino/core_modules/components)
-`Collect` and `Transform` paradigms are currently only used in orchestration scenarios.
+Collect and Transform two streaming paradigms are currently only used in orchestration scenarios.
-## Streaming Across Multiple Components (Orchestration)
+## Streaming from Multiple Component Orchestration Perspective
-### Component Streaming Paradigms in Orchestration
+### Component Streaming Paradigm in Orchestration
-When a component is used standalone, its input and output streaming paradigms are fixed and cannot exceed the scope defined by the component interface.
+A component, when used standalone, has fixed input and output streaming paradigms that cannot exceed the range defined by the component's interface.
-- For example, ChatModel's input can only be non-streaming `[]Message`, and its output can be either non-streaming `Message` or streaming `StreamReader[Message]`, because ChatModel only implements the `Invoke` and `Stream` paradigms.
+- For example, Chat Model's input can only be non-streaming []Message, output can be either non-streaming Message or streaming StreamReader[Message], because Chat Model only implements Invoke and Stream paradigms.
-However, once a component is in an "orchestration" scenario where multiple components are combined, its input and output are no longer fixed, but depend on the "upstream output" and "downstream input" in the orchestration context. Consider a typical ReAct Agent orchestration diagram:
+However, once a component is in an "orchestration" scenario where multiple components are used together, its input and output are no longer so fixed, but depend on the "upstream output" and "downstream input" of this component in the orchestration scenario. For example, the typical orchestration diagram of React Agent:
-In the diagram above, if Tool is a StreamableTool (i.e., output is `StreamReader[Message]`), then Tool → ChatModel could have streaming output. However, ChatModel has no business scenario for accepting streaming input, nor does it have a corresponding interface. At this point, Eino framework automatically provides ChatModel with the ability to accept streaming input:
+In the figure above, if Tool is a StreamableTool, i.e., output is StreamReader[Message], then Tool -> ChatModel could be streaming output. But Chat Model doesn't have a business scenario for receiving streaming input, nor a corresponding interface. At this point, the Eino framework automatically helps ChatModel complete the ability to receive streaming input:
-The "Concat message stream" above is a capability automatically provided by the Eino framework. Even if it's not a message but any arbitrary `T`, as long as certain conditions are met, Eino framework will automatically perform this `StreamReader[T]` to `T` conversion. The condition is: **In orchestration, when a component's upstream output is `StreamReader[T]`, but the component only provides a business interface with `T` as input, the framework will automatically concat `StreamReader[T]` into `T` before inputting it to the component.**
+The Concat message stream above is automatically provided by the Eino framework. Even if it's not message, but any T, as long as specific conditions are met, the Eino framework will automatically do this StreamReader[T] to T conversion. The condition is: **In orchestration, when a component's upstream output is StreamReader[T], but the component only provides a business interface with T as input, the framework will automatically concat StreamReader[T] into T, then input to this component.**
> 💡
-> The process of the framework automatically concatenating `StreamReader[T]` into `T` may require the user to provide a concat function. See [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles) for the section on "merge frames".
+> The process of framework automatically concatenating StreamReader[T] into T may require users to provide a Concat function. See the chapter on "merging frames" in [Eino: Orchestration Design Principles](/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles#share-FaVnd9E2foy4fAxtbTqcsgq3n5f).
-On the other hand, consider an opposite example. Still the ReAct Agent, this time with a more complete orchestration diagram:
+On the other hand, consider an opposite example. Still React Agent, this time a more complete orchestration diagram:
-In the diagram above, the branch receives the message output from the ChatModel and decides whether to end the agent's current run and output the message directly, or call the Tool and pass the result back to the ChatModel for cyclic processing, based on whether the message contains a tool call. Since this Branch can complete its logic judgment from the first frame of the message stream, we define this Branch with the `Collect` interface, i.e., streaming input, non-streaming output:
+In the figure above, branch receives message output by chat model and, based on whether the message contains tool calls, chooses whether to directly end the agent's current run and output the message, or call Tool and give the call result to Chat Model for loop processing. Since this Branch can complete logic judgment from the first frame of the message stream, we define this Branch with a Collect interface, i.e., streaming input, non-streaming output:
```go
compose.NewStreamGraphBranch(func(ctx context.Context, sr *schema.StreamReader[*schema.Message]) (endNode string, err error) {
@@ -122,40 +122,40 @@ compose.NewStreamGraphBranch(func(ctx context.Context, sr *schema.StreamReader[*
}
return nodeKeyTools, nil
-})
+}
```
-ReactAgent has two interfaces, `Generate` and `Stream`, which implement the `Invoke` and `Stream` streaming programming paradigms respectively. When a ReactAgent is called via `Stream`, the ChatModel's output is `StreamReader[Message]`, so the Branch's input is `StreamReader[Message]`, which matches the function signature definition of this Branch condition, and can run without any conversion.
+ReactAgent has two interfaces, Generate and Stream, implementing Invoke and Stream streaming programming paradigms respectively. When a ReactAgent is called with Stream, Chat Model's output is StreamReader[Message], so Branch's input is StreamReader[Message], matching this Branch condition's function signature definition, can run without any conversion.
-However, when this ReactAgent is called via `Generate`, the ChatModel's output is `Message`, so the Branch's input would also be `Message`, which does not match the `StreamReader[Message]` function signature definition of the Branch Condition. At this point, the Eino framework will automatically box `Message` into `StreamReader[Message]` and pass it to the Branch, and this StreamReader will only have one frame.
+However, when this ReactAgent is called with Generate, Chat Model's output is Message, so Branch's input will also be Message, not matching Branch Condition's StreamReader[Message] function signature definition. At this point, the Eino framework will automatically box Message into StreamReader[Message], then pass to Branch, and this StreamReader will only have one frame.
> 💡
-> This kind of stream with only one frame is colloquially called a "pseudo-stream", because it doesn't bring the actual benefit of streaming, which is "low first-packet latency", but is simply boxed to meet the streaming input/output interface signature requirements.
+> This kind of stream with only one frame is commonly called "fake stream", because it doesn't bring the actual benefit of streaming, i.e., "low first packet latency", but is just simple boxing to meet the streaming input/output interface signature requirements.
-In summary: **In orchestration, when a component's upstream output is `T`, but the component only provides a business interface with `StreamReader[T]` as input, the framework will automatically box `T` into a single-frame `StreamReader[T]` before inputting it to the component.**
+In summary: **In orchestration, when a component's upstream output is T, but the component only provides a business interface with StreamReader[T] as input, the framework will automatically box T into a single-frame StreamReader[T], then input to this component.**
-### Streaming Paradigms of Orchestration Aids
+### Streaming Paradigms of Orchestration Helper Elements
-The Branch mentioned above is not a component that can be used standalone, but an "orchestration aid" that only makes sense in orchestration scenarios. Similar "components" that are only meaningful in orchestration scenarios include:
+The Branch mentioned above is not a standalone component, but an "orchestration helper element" that only makes sense in orchestration scenarios. There are some similar "components" that only make sense in orchestration scenarios, see the figure below:
-| Element | Use Case | Invoke | Stream | Collect | Transform |
-| Branch | Dynamically select one from a set of downstream Nodes based on upstream outputIf decision can only be made after receiving complete input → implement InvokeIf decision can be made after receiving partial frames → implement CollectOnly one can be implemented | yes | no | yes | no |
-| StatePreHandler | Modify State and/or Input before entering a Node in Graph. Supports streaming. | yes | no | no | yes |
-| StatePostHandler | Modify State and/or Output after a Node completes in Graph. Supports streaming. | yes | no | no | yes |
-| Passthrough | In parallel scenarios, to balance the number of Nodes in each parallel branch, Passthrough nodes can be added to branches with fewer Nodes. Passthrough node's input and output are the same, following the upstream node's output or downstream node's input (expected to be the same). | yes | no | no | yes |
-| Lambda | Encapsulate business logic not defined by official components. Choose the corresponding streaming paradigm based on which paradigm the business logic is. | yes | yes | yes | yes |
+| Component Name | Usage Scenario | Implements Invoke | Implements Stream | Implements Collect | Implements Transform |
+| Branch | Dynamically select one from a group of downstream Nodes based on upstream outputFor cases that can only judge after receiving complete input, implement InvokeFor cases that can judge after receiving partial frames, implement CollectOnly one can be implemented | yes | no | yes | no |
+| StatePreHandler | In Graph, modify State or/and Input before entering Node. Can support streaming. | yes | no | no | yes |
+| StatePostHandler | In Graph, modify State or/and Output after Node completes. Can support streaming | yes | no | no | yes |
+| Passthrough | In parallel situations, to flatten the number of Nodes in each parallel branch, can add Passthrough nodes to branches with fewer Nodes. Passthrough node's input and output are the same, following upstream node's output or following downstream node's input (expected to be the same). | yes | no | no | yes |
+| Lambda | Encapsulate business logic not defined by official components. Whatever paradigm the business logic is, choose the corresponding streaming paradigm to implement. | yes | yes | yes | yes |
-Additionally, there's another type of "component" that only makes sense in orchestration scenarios: treating orchestration artifacts as a whole, such as compiled Chain, Graph. These overall orchestration artifacts can be called as "components" standalone, or added as nodes to higher-level orchestration artifacts.
+There's another type of "component" that only makes sense in orchestration scenarios, which is viewing orchestration products as a whole, like orchestrated Chain, Graph. These overall orchestration products can be called as "components" standalone, or can be added as nodes to higher-level orchestration products.
-## Streaming at Orchestration Level (Whole Graph)
+## Streaming from Overall Orchestration Perspective
-### "Business" Paradigms of Orchestration Artifacts
+### Orchestration Product's "Business" Paradigm
-Since overall orchestration artifacts can be viewed as "components", from a component perspective we can ask: do orchestration artifact "components" have interface paradigms that match "business scenarios" like ChatModel and other components? The answer is both "yes" and "no".
+Since overall orchestration products can be viewed as a "component", from the component perspective we can ask: Does the orchestration product as a "component" have interface paradigms that fit "business scenarios" like Chat Model and other components? The answer is both "yes" and "no".
-- "No": Overall, Graph, Chain and other orchestration artifacts have no business attributes themselves, they only serve abstract orchestration, so there are no interface paradigms that match business scenarios. At the same time, orchestration needs to support various paradigm business scenarios. Therefore, the `Runnable[I, O]` interface representing orchestration artifacts in Eino makes no choice and cannot choose, providing methods for all streaming paradigms:
+- "No": Overall, orchestration products like Graph, Chain have no business attributes themselves, only serving abstract orchestration, so there's no interface paradigm matching business scenarios. Also, orchestration needs to support various business scenario paradigms. So, Eino's Runnable[I, O] interface representing orchestration products, without making choices and unable to choose, provides all streaming paradigm methods:
```go
type Runnable[I, O any] interface {
@@ -166,57 +166,57 @@ type Runnable[I, O any] interface {
}
```
-- "Yes": Specifically, a particular Graph or Chain must carry specific business logic, so there must be streaming paradigms suitable for that specific business scenario. For example, a Graph similar to ReactAgent matches the business scenarios of `Invoke` and `Stream`, so the logical calling methods for this Graph are `Invoke` and `Stream`. Although the orchestration artifact interface `Runnable[I, O]` itself has `Collect` and `Transform` methods, normal business scenarios don't need to use them.
+- "Yes": Specifically, a specific Graph or Chain must carry specific business logic, so there must be a streaming paradigm suitable for that specific business scenario. For example, a Graph similar to React Agent, matching business scenarios are Invoke and Stream, so the logical calling methods for this Graph are Invoke and Stream. Although the orchestration product's interface Runnable[I, O] has Collect and Transform methods, normal business scenarios don't need to use them.
-### Runtime Paradigms of Components Inside Orchestration Artifacts
+### Paradigm of Internal Components at Runtime in Orchestration Products
-From another perspective, since orchestration artifacts as a whole can be viewed as "components", "components" must have their own internal implementation. For example, the internal implementation logic of ChatModel might be to convert the input `[]Message` into API requests for various models, then call the model's API, and convert the response into the output `Message`. By analogy, what is the internal implementation of the Graph "component"? It's data flowing among the various components inside the Graph in the user-specified flow direction and streaming paradigms. "Flow direction" is not within the current discussion scope, while the streaming paradigms of each component at runtime are determined by the overall triggering method of the Graph. Specifically:
+From another perspective, since orchestration products as a whole can be viewed as "components", then "components" must have their own internal implementation. For example, ChatModel's internal implementation logic might be converting input []Message to various model API requests, then calling the model API, and converting the response to output Message. So by analogy, what is Graph as a "component"'s internal implementation? It's data flowing between Graph's internal components in user-specified flow directions and streaming paradigms. Among them, "flow direction" is not within current discussion scope, while each component's streaming paradigm at runtime is determined by Graph's overall trigger method, specifically:
-If the user calls the Graph via **Invoke**, all internal components are called with the `Invoke` paradigm. If a component does not implement the `Invoke` paradigm, the Eino framework automatically wraps the `Invoke` calling paradigm based on the streaming paradigms the component has implemented, with the following priority:
+If user calls Graph through **Invoke**, all internal Graph components are called with Invoke paradigm. If a component hasn't implemented Invoke paradigm, Eino framework automatically wraps Invoke calling paradigm based on the streaming paradigm the component has implemented, with priority order as follows:
-- If the component implements `Stream`, wrap `Stream` as `Invoke`, i.e., automatically concat the output stream.
+- If component implements Stream, wrap Stream into Invoke, i.e., automatically concat output stream.
-- Otherwise, if the component implements `Collect`, wrap `Collect` as `Invoke`, i.e., convert non-streaming input to single-frame stream.
+- Otherwise, if component implements Collect, wrap Collect into Invoke, i.e., non-streaming input converts to single-frame stream.
-- If neither is implemented, the component must implement `Transform`, wrap `Transform` as `Invoke`, i.e., convert input to single-frame stream and concat output.
+- If neither is implemented, must implement Transform, wrap Transform into Invoke, i.e., input converts to single-frame stream, output concats.
-If the user calls the Graph via **Stream/Collect/Transform**, all internal components are called with the `Transform` paradigm. If a component does not implement the `Transform` paradigm, the Eino framework automatically wraps the `Transform` calling paradigm based on the streaming paradigms the component has implemented, with the following priority:
+If user calls Graph through **Stream/Collect/Transform**, all internal Graph components are called with Transform paradigm. If a component hasn't implemented Transform paradigm, Eino framework automatically wraps Transform calling paradigm based on the streaming paradigm the component has implemented, with priority order as follows:
-- If the component implements `Stream`, wrap `Stream` as `Transform`, i.e., automatically concat the input stream.
+- If component implements Stream, wrap Stream into Transform, i.e., automatically concat input stream.
-- Otherwise, if the component implements `Collect`, wrap `Collect` as `Transform`, i.e., convert non-streaming output to single-frame stream.
+- Otherwise, if component implements Collect, wrap Collect into Transform, i.e., non-streaming output converts to single-frame stream.
-- If neither is implemented, the component must implement `Invoke`, wrap `Invoke` as `Transform`, i.e., concat input stream and convert output to single-frame stream.
+- If neither is implemented, must implement Invoke, wrap Invoke into Transform, i.e., input stream concats, output converts to single-frame stream
-Combining the various cases enumerated above, Eino framework's automatic conversion between `T` and `Stream[T]` can be summarized as:
+Combining the various cases exhaustively listed above, Eino framework's automatic conversion of T and Stream[T] can be summarized as:
-- **T -> Stream[T]: Box the complete `T` into a single-frame `Stream[T]`. Non-streaming becomes pseudo-streaming.**
-- **Stream[T] -> T: Concat `Stream[T]` into a complete `T`. When `Stream[T]` is not a single-frame stream, a concat method for `T` may need to be provided.**
+- **T -> Stream[T]: Box complete T into single-frame Stream[T]. Non-streaming becomes fake streaming.**
+- **Stream[T] -> T: Concat Stream[T] into complete T. When Stream[T] is not single-frame stream, may need to provide Concat method for T.**
-After seeing the implementation principles above, you might have questions: why does calling `Invoke` on a graph require all internal components to be called with `Invoke`? And why does calling `Stream/Collect/Transform` on a graph require all internal components to be called with `Transform`? After all, some counterexamples can be given:
+After seeing the above implementation principles, there might be questions about why Invoke on graph requires all internal components to be called with Invoke? And why Stream/Collect/Transform on graph requires all internal components to be called with Transform? After all, counterexamples can be given:
-- Components A and B are orchestrated into a Chain, called with `Invoke`. A's business interface implements `Stream`, B's business interface implements `Collect`. At this point, there are two choices for the calling paradigm of internal components in the graph:
- - A is called with `Stream`, B is called with `Collect`, the overall Chain still has `Invoke` semantics, while preserving true streaming internal semantics. That is, A's output stream doesn't need to be concatenated and can be input to B in real-time.
- - Current Eino implementation: A and B are both called with `Invoke`, requiring A's output stream to be concatenated and B's input to be made into a pseudo-stream. True streaming internal semantics are lost.
-- Components A and B are orchestrated into a Chain, called with `Collect`. A implements `Transform` and `Collect`, B implements `Invoke`. Two choices:
- - A is called with `Collect`, B is called with `Invoke`: the overall still has `Collect` semantics, no automatic conversion or boxing operations are needed by the framework.
- - Current Eino implementation: A and B are both called with `Transform`. Since A's business interface implements `Transform`, both A's output and B's input could be true streaming, but B's business interface only implements `Invoke`. According to the analysis above, B's input will need to be concatenated from true streaming to non-streaming. At this point, the user needs to additionally provide a concat function for B's input, which could have been avoided.
+- Two components A, B orchestrated into a Chain, called with Invoke. A's business interface implements Stream, B's business interface implements Collect. At this point, there are two choices for graph internal component calling paradigms:
+ - A is called with stream, B is called with collect, the overall Chain still has Invoke semantics, while preserving true streaming internal semantics. I.e., A's output stream doesn't need Concat, can be input to B in real-time.
+ - Current Eino implementation, both A and B are called with Invoke, need to Concat A's output stream, and make B's input fake streaming. Loses true streaming internal semantics.
+- Two components A, B orchestrated into a Chain, called with Collect. A implements Transform and Collect, B implements Invoke. Two choices:
+ - A is called with Collect, B is called with Invoke: overall still has Collect semantics, no need for framework to do any automatic conversion or boxing operations.
+ - Current Eino implementation, both A and B are called with Transform. Since A's business interface implements Transform, both A's output and B's input may be true streaming. But B's business interface only implements Invoke, based on above analysis, B's input needs to be concatenated from true streaming to non-streaming. At this point, users need to additionally provide B's input concat function, which could have been avoided.
-The two examples above can both find a clear, different from Eino's convention, but better streaming call path. However, when generalized to arbitrary orchestration scenarios, it's difficult to find a clearly defined, different from Eino's convention, yet always better universal rule. For example, A->B->C, called with `Collect` semantics, should `Collect` happen at A->B or B->C? Potential factors include the business interfaces specifically implemented by A, B, C, possibly the judgment of "use true streaming as much as possible", and maybe which parameters implement `Concat` and which don't. If it's a more complex Graph, the factors to consider will increase rapidly. In this situation, even if the framework can define a clear, better universal rule, it would be hard to explain clearly, and the understanding and usage cost would be high, likely already exceeding the actual benefit brought by this new rule.
+Both examples above can find a clear, different from Eino's convention, but better streaming calling path. However, when generalized to arbitrary orchestration scenarios, it's hard to find a clearly defined, different from Eino's convention, yet always better universal rule. For example, A->B->C, called with Collect semantics, is it A->B that does Collect, or B->C? Potential factors include specific business interfaces implemented by A, B, C, possibly also "use as much true streaming as possible" judgment, maybe also which parameter implements Concat and which doesn't. For more complex Graphs, factors to consider increase rapidly. In this situation, even if the framework can define a set of clear, better universal rules, it would be hard to explain clearly, understanding and usage costs would be high, likely already exceeding the actual benefits brought by this new rule.
-In summary, we can say that the runtime paradigms of components inside Eino orchestration artifacts are **By Design**, clearly as follows:
+In summary, we can say the paradigm of each component at runtime within Eino orchestration products is **By Design**, specifically:
-- **Called with `Invoke` overall, all internal components are called with `Invoke`, there is no streaming process.**
-- **Called with `Stream/Collect/Transform` overall, all internal components are called with `Transform`. When `Stream[T] -> T` concat process occurs, a concat function for `T` may need to be additionally provided.**
+- **Called overall with Invoke, internal components all called with Invoke, no streaming process exists.**
+- **Called overall with Stream/Collect/Transform, internal components all called with Transform. When Stream[T] -> T concat process occurs, may need to additionally provide T's concat function.**
diff --git a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/workflow_orchestration_framework.md b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/workflow_orchestration_framework.md
index ea063b20fa0..7b69f914dd1 100644
--- a/content/en/docs/eino/core_modules/chain_and_graph_orchestration/workflow_orchestration_framework.md
+++ b/content/en/docs/eino/core_modules/chain_and_graph_orchestration/workflow_orchestration_framework.md
@@ -1,15 +1,15 @@
---
Description: ""
-date: "2026-01-20"
+date: "2026-03-02"
lastmod: ""
tags: []
title: 'Eino: Workflow Orchestration Framework'
weight: 3
---
-## What Is Eino Workflow?
+## What is Eino Workflow
-An orchestration API at the same architectural level as Graph:
+It's a set of orchestration APIs at the same architectural level as Graph API:
```mermaid
flowchart LR
@@ -22,64 +22,64 @@ flowchart LR
G --> C
```
-Key traits:
+Essential characteristics:
-- Same capability level as Graph; both orchestrate “LLM-centric information flow”.
- - Node types, streaming, callbacks, options, state, interrupt & checkpoint are consistent.
- - Implements `AnyGraph`; can be added as a child to Graph/Chain/Workflow via `AddGraphNode`.
- - Can host other Graph/Chain/Workflow as children.
-- Field-level mapping: node input can be a composition of arbitrary fields from arbitrary predecessors.
- - Natively supports `struct`, `map`, and arbitrarily nested combinations.
-- Control flow and data flow can be separated. In Graph, edges carry both; in Workflow, you may split them.
-- No cycles (e.g., ChatModel→ToolsNode→ChatModel loops). `NodeTriggerMode` is fixed to `AllPredecessor`.
+- Has the same level of capability as Graph API, both are suitable framework tools for orchestrating "information flow around large models".
+ - Maintains consistency in node types, stream processing, callback, option, state, interrupt & checkpoint, etc.
+ - Implements AnyGraph interface, can be added as a sub-graph to parent Graph/Chain/Workflow through AddGraphNode.
+ - Can also add other Graph/Chain/Workflow as its own sub-graphs.
+- Field-level mapping capability: A node's input can be composed of any output fields from any predecessor nodes.
+ - Natively supports struct, map, and mutual mapping between structs and maps of any nesting level.
+- Separation of control flow and data flow: Graph's Edge determines both execution order and data transfer. In Workflow, they can be transferred together or separately.
+- Does not support cycles (i.e., loops like chatmodel->toolsNode->chatmodel in react agent). NodeTriggerMode is fixed to AllPredecessor.
-## Why Workflow?
+## Why Use Workflow
-### Flexible Input/Output Types
+### Flexible Input and Output Types
-For example, you need to orchestrate two lambda nodes containing two "existing business functions f1, f2" with specific struct inputs/outputs suited to business scenarios, each different:
+For example, when orchestrating two lambda nodes containing two "existing business functions f1, f2", whose input and output types are specific structs matching business scenarios, each different:
-When orchestrating with Workflow, map f1's output field F1 directly to f2's input field F3, while preserving the original function signatures of f1 and f2. The effect achieved is: **each node's input/output is "determined by the business scenario", without needing to consider "who provides my input and who uses my output"**.
+When orchestrating with Workflow, map f1's output field F1 directly to f2's input field F3, while keeping f1, f2's original function signatures. The effect achieved is: **Each node is "business scenario determines input and output", no need to consider "who gives me input, and who uses my output"**.
-When orchestrating with Graph, due to the "type alignment" requirement, if f1 → f2, then f1's output type and f2's input type need to align. You must choose one of two options:
+When orchestrating with Graph, due to the "type alignment" requirement, if f1 -> f2, then f1's output type and f2's input type need to be aligned, requiring one of two choices:
-- Define a new common struct, and change both f1's output type and f2's input type to this common struct. This has costs and may intrude on business logic.
-- Change both f1's output type and f2's input type to map. This loses the strong typing alignment characteristic.
+- Define a new common struct, change both f1's output type and f2's input type to this common struct. Has cost, may intrude on business logic.
+- Change both f1's output type and f2's input type to map. Loses the strong type alignment characteristic.
-### Separate Control and Data Flow
+### Separation of Control Flow and Data Flow
-Consider the following scenario:
+Look at the following scenario:
-Node D references certain output fields from A, B, and C simultaneously. The dashed line from A to D is purely "data flow", not carrying "control" information - meaning whether A completes execution does not determine whether D starts execution.
+Node D simultaneously references certain output fields from A, B, C. The dashed line from A to D is purely "data flow", not transmitting "control" information, meaning A's execution status doesn't determine whether D can start executing.
-The bold arrow from node D to E represents that node E does not reference any output from node D - it is purely "control flow", not carrying "data". Whether D completes execution determines whether E starts execution, but D's output does not affect E's input.
+The thick arrow from node D to E represents pure "control flow", not transmitting "data". Meaning D's completion status determines whether E starts executing, but D's output doesn't affect E's input.
-Other lines in the diagram combine control flow and data flow.
+Other lines in the figure combine control flow and data flow.
-Note that data flow can only be transmitted when a control flow path exists. For example, the A→D data flow depends on the existence of A→branch→B→D or A→branch→C→D control flow. Data flow can only reference outputs from predecessor nodes.
+Note that the premise for data flow transfer is that a control flow must exist. For example, the data flow from A->D depends on control flow from A->branch->B->D or A->branch->C->D existing. Data flow can only reference predecessor nodes' outputs.
-For example, this "cross-node" specific data passing scenario:
+For example, this scenario of "cross-node" transferring specific data:
-In the diagram above, the chat template node's input can be very explicit:
+In the figure above, the chat template node's input can be very explicit:
`map[string]any{"prompt": "prompt from START", "context": "retrieved context"}`
-In contrast, if using Graph or Chain API, you must choose one of two options:
+In contrast, if using Graph or Chain API, one of two choices is needed:
-- Use OutputKey to convert node output types (can't add to START node, so need an extra passthrough node), and the ChatTemplate node's input will include the full output of START and retriever (rather than just the specific fields actually needed).
-- Put START node's prompt in state, and ChatTemplate reads from state. This introduces additional state.
+- Use OutputKey to convert node output type (can't add to START node, so need to add extra passthrough node). ChatTemplate node's input will include full output from START and retriever (not just the actually needed fields).
+- Put START node's prompt in state, ChatTemplate reads from state. Additionally introduces state.
-## Using Workflow
+## How to Use Workflow
### Simplest Workflow
-START → node → END
+START -> node -> END
@@ -132,30 +132,30 @@ func main() {
Core APIs:
- `func NewWorkflow[I, O any](opts ...NewGraphOption) *Workflow[I, O]`
- - Creates a new Workflow.
- - Signature is identical to `NewGraph`.
-- `func (wf *Workflow[I, O]) AddChatModelNode(key string, chatModel model.BaseChatModel, opts ...GraphAddNodeOpt) *WorkflowNode`
- - Adds a new node to the Workflow.
- - Supported node types are identical to Graph.
- - Unlike Graph's AddXXXNode which returns an error immediately, Workflow defers error handling to the final Compile step.
- - AddXXXNode returns a WorkflowNode, allowing subsequent field mapping operations via method chaining.
+ - Build a new Workflow.
+ - Signature completely identical to `NewGraph`.
+- `func (wf *Workflow[I, O]) AddChatModelNode(key string, chatModel model.BaseChatModel, opts ...GraphAddNodeOpt) *WorkflowNode `
+ - Add a new node to Workflow.
+ - Node types that can be added are completely identical to Graph.
+ - Difference from Graph's AddXXXNode is that Workflow doesn't return error immediately, but handles and returns errors uniformly at final Compile.
+ - AddXXXNode returns a WorkflowNode, subsequent operations like adding field mappings to the Node are done directly with Method Chaining
- `func (n *WorkflowNode) AddInput(fromNodeKey string, inputs ...*FieldMapping) *WorkflowNode`
- - Adds input field mappings to a WorkflowNode.
- - Returns WorkflowNode for continued method chaining.
+ - Add input field mappings to a WorkflowNode
+ - Returns WorkflowNode, can continue Method Chaining.
- `(wf *Workflow[I, O]) Compile(ctx context.Context, opts ...GraphCompileOption) (Runnable[I, O], error)`
- - Compiles a Workflow.
- - Signature is identical to compiling a Graph.
+ - Compile a Workflow.
+ - Signature completely identical to Compile Graph.
### Field Mapping
-START (struct input) → [parallel lambda c1, c2] → END (map output).
+START (input struct) -> [parallel lambda1, lambda2] -> END (output map).
-We demonstrate counting occurrences of a substring in two different fields. The workflow input is an Eino `Message` plus a `SubStr`; `c1` counts occurrences in `Content`, `c2` counts occurrences in `ReasoningContent`. The two lambdas run in parallel and map their results to END.
-
-In the diagram below, the workflow's overall input is a message struct, both `c1` and `c2` lambdas have counter struct inputs, both output int, and the workflow's overall output is `map[string]any`:
+Let's use an example of "counting character occurrences in a string". The workflow overall inputs an eino Message and a sub string, gives Message.Content to a counter c1, gives Message.ReasoningContent to another counter c2, calculates sub string occurrence counts in parallel, then maps them to END respectively:
+In the figure above, workflow's overall input is a message struct, c1 and c2 lambdas' inputs are both counter struct, outputs are both int, workflow's overall output is map[string]any. Code as follows:
+
```go
// demonstrates the field mapping ability of eino workflow.
func main() {
@@ -164,7 +164,7 @@ func main() {
SubStr string // exported because we will do field mapping for this field
}
- // wordCounter is a lambda function that counts occurrences of SubStr within FullStr
+ // wordCounter is a lambda function that count occurrences of SubStr within FullStr
wordCounter := func(ctx context.Context, c counter) (int, error) {
return strings.Count(c.FullStr, c.SubStr), nil
}
@@ -188,15 +188,17 @@ func main() {
// add lambda c2 just like in Graph
wf.AddLambdaNode("c2", compose.InvokableLambda(wordCounter)).
AddInput(compose.START, // add an input from START, specifying 2 field mappings
- // map START's SubStr field to lambda c2's SubStr field
+ // map START's SubStr field to lambda c1's SubStr field
compose.MapFields("SubStr", "SubStr"),
- // map START's Message's ReasoningContent field to lambda c2's FullStr field
+ // map START's Message's ReasoningContent field to lambda c1's FullStr field
compose.MapFieldPaths([]string{"Message", "ReasoningContent"}, []string{"FullStr"}))
wf.End(). // Obtain the compose.END for method chaining
- // add an input from c1, mapping full output of c1 to the map key 'content_count'
+ // add an input from c1,
+ // mapping full output of c1 to the map key 'content_count'
AddInput("c1", compose.ToField("content_count")).
- // also add an input from c2, mapping full output of c2 to the map key 'reasoning_content_count'
+ // also add an input from c2,
+ // mapping full output of c2 to the map key 'reasoning_content_count'
AddInput("c2", compose.ToField("reasoning_content_count"))
// compile the workflow just like compiling a Graph
@@ -224,31 +226,31 @@ func main() {
}
```
-[Eino example link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/2_field_mapping/main.go)
+[Eino example code link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/2_field_mapping/main.go)
-The `AddInput` method accepts 0–n field mappings and can be called multiple times. This means:
+The main information from this example is that the `AddInput` method can pass 0-n field mapping rules, and can call `AddInput` multiple times. This means:
-- A node can reference any number of fields from a single predecessor’s output.
-- A node can reference fields from any number of predecessors.
-- A mapping can be whole→field, field→whole, whole→whole, or nested field↔nested field.
-- Different mapping types have different APIs:
- - Top-level field → top-level field: `MapFields(string, string)`
- - Whole output → top-level field: `ToField(string)`
- - Top-level field → whole input: `FromField(string)`
- - Nested field → nested field: `MapFieldPaths(FieldPath, FieldPath)`
- - Whole output → nested field: `ToFieldPath(FieldPath)`
- - Nested field → whole input: `FromFieldPath(FieldPath)`
- - Whole output → whole input: `AddInput(fromKey)` with no `FieldMapping`
+- A node can reference any number of fields from one predecessor node's output.
+- A node can reference fields from any number of predecessor nodes.
+- A mapping can be "whole to field", "field to whole", "whole to whole", or mapping between nested fields.
+- Different types above have different APIs to express the mapping:
+ - Top-level field to top-level field: `MapFields(string, string)`
+ - Full output to top-level field: `ToField(string)`
+ - Top-level field to full input: `FromField(string)`
+ - Nested field to nested field: `MapFieldPaths(FieldPath, FieldPath)`, needed when either upstream or downstream is nested
+ - Full output to nested field: `ToFieldPath(FieldPath)`
+ - Nested field to full input: `FromFieldPath(FieldPath)`
+ - Full output to full input: Just use `AddInput`, no need to pass `FieldMapping`
## Advanced Features
-### Data-only Edges (No Control Flow)
+### Data Flow Only, No Control Flow
-Imagine a simple scenario: START → adder node → multiplier node → END. The "multiplier node" multiplies one field from START with the result from the adder node:
+Imagine a simple scenario: START -> add node -> multiply node -> END. Where "multiply node" multiplies a field from START with the result from add node:
-In the diagram above, the multiplier node executes after the adder node, meaning the "multiplier node" is controlled by the "adder node". However, the START node does not directly control the "multiplier node"; it only passes data to it. In code, use `AddInputWithOptions(fromNode, fieldMappings, WithNoDirectDependency)` to specify a pure data flow:
+In the figure above, multiply node executes after add node, i.e., "multiply node" is controlled by "add node". But START node doesn't directly control "multiply node", only passes data over. In code, use `AddInputWithOptions(fromNode, fieldMappings, WithNoDirectDependency)` to specify pure data flow:
```go
func main() {
@@ -307,7 +309,7 @@ func main() {
}
```
-[Eino examples link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/3_data_only/main.go)
+[Eino examples code link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/3_data_only/main.go)
New API introduced in this example:
@@ -317,7 +319,7 @@ func (n *WorkflowNode) AddInputWithOptions(fromNodeKey string, inputs []*FieldMa
}
```
-And the new Option:
+And new Option:
```go
func WithNoDirectDependency() WorkflowAddInputOpt {
@@ -327,19 +329,19 @@ func WithNoDirectDependency() WorkflowAddInputOpt {
}
```
-Combined, these can add pure "data dependency relationships" to nodes.
+Combined, can add pure "data dependency relationship" to nodes.
-### Control-only Edges (No Data Flow)
+### Control Flow Only, No Data Flow
-Imagine a "sequential bidding with confidential prices" scenario: START → bidder1 → threshold check → bidder2 → END:
+Imagine an "sequential bidding, but price confidential" scenario: START -> bidder 1 -> is qualified -> bidder 2 -> END:
-In the diagram above, regular lines are "control + data", dashed lines are "data only", and bold lines are "control only". The logic is: input an initial price, bidder1 makes bid1, a branch checks if it's high enough - if so, end directly; otherwise, pass the initial price to bidder2 for bid2, and finally aggregate both bids for output.
+In the figure above, normal lines are "control + data", dashed lines are "data only", bold lines are "control only". Logic is: input an initial price, bidder 1 gives bid 1, branch judges if it's high enough, if high enough then end directly, otherwise give initial price to bidder 2, give bid 2, finally summarize bids 1 and 2 for output.
-After bidder1 bids, an announcement is made: "bidder completed bidding". Note that bidder1→announcer is a bold solid line, "control only", because the amount must be kept confidential when announcing!
+After bidder 1 gives bid, announce "bidder completed bidding". Note bidder1->announcer is bold line, "control only", because the price must be kept confidential when announcing!
-The two bold lines from the branch are both "control only" because neither bidder2 nor END depends on data from the branch. In code, use `AddDependency(fromNode)` to specify pure control flow:
+Both bold lines from branch are "control only", because neither bidder2 nor END depends on data from branch. In code, use `AddDependency(fromNode)` to specify pure control flow:
```go
func main() {
@@ -402,7 +404,7 @@ func main() {
}
```
-[Eino examples link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/4_control_only_branch/main.go)
+[Eino examples code link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/4_control_only_branch/main.go)
New API introduced in this example:
@@ -412,11 +414,11 @@ func (n *WorkflowNode) AddDependency(fromNodeKey string) *WorkflowNode {
}
```
-You can use `AddDependency` to specify pure "control dependency relationships" for nodes.
+Can specify pure "control dependency relationship" for nodes through AddDependency.
### Branch
-In the example above, we added a branch in almost the same way as with the Graph API:
+In the example above, we added a branch in almost exactly the same way as Graph API:
```go
// add a branch just like adding branch in Graph.
@@ -428,20 +430,20 @@ In the example above, we added a branch in almost the same way as with the Graph
}, map[string]bool{compose.END: true, "b2": true}))
```
-Branch semantics are the same as Graph's AllPredecessor mode:
+Branch semantics are the same as branch semantics in Graph's AllPredecessor mode:
-- There is exactly one 'fromNode', meaning a branch can only have one predecessor control node.
-- Can be single-select (`NewGraphBranch`) or multi-select (`NewGraphMultiBranch`).
-- Selected branches can execute. Unselected branches are marked as skip.
-- A node can only execute when all incoming edges are complete (success or skip), and at least one edge succeeded. (Like END in the example above)
-- If all incoming edges of a node are skip, all outgoing edges of that node are automatically marked as skip.
+- Has one and only one 'fromNode', i.e., a branch's preceding control node can only be one.
+- Can single-select (NewGraphBranch), can multi-select (NewGraphMultiBranch).
+- Branch-selected branches are executable. Unselected branches are marked as skip.
+- A node can only execute when all incoming edges are complete (success or skip), and at least one edge succeeds. (Like END in the example above)
+- If all incoming edges of a node are skip, all outgoing edges of this node are automatically marked as skip.
-Additionally, there is one key difference between workflow branch and graph branch:
+Meanwhile, workflow branch has one core difference from graph branch:
-- Graph branch is always "control and data combined"; the downstream node's input of a branch is always the output of the branch's fromNode.
-- Workflow branch is always "control only"; the downstream node's input is specified via `AddInputWithOptions`.
+- Graph branch is always "control and data combined", branch downstream node's input is always branch fromNode's output.
+- Workflow branch is always "control only", branch downstream node's input is specified through AddInputWithOptions.
-Related API:
+New API involved:
```go
func (wf *Workflow[I, O]) AddBranch(fromNodeKey string, branch *GraphBranch) *WorkflowBranch {
@@ -455,11 +457,11 @@ func (wf *Workflow[I, O]) AddBranch(fromNodeKey string, branch *GraphBranch) *Wo
}
```
-Signature is almost identical to `Graph.AddBranch`, allowing you to add a branch to the workflow.
+Signature almost completely identical to Graph.AddBranch, can add a branch to workflow.
### Static Values
-Let's modify the "bidding" example above by giving bidder1 and bidder2 each a static "budget" configuration:
+Let's modify the "bidding" example above, giving bidders 1 and 2 each a "budget" static configuration:
@@ -522,9 +524,9 @@ func main() {
}
```
-[Eino examples link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/5_static_values/main.go)
+[Eino examples code link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/5_static_values/main.go)
-Related API:
+New API involved here:
```go
func (n *WorkflowNode) SetStaticValue(path FieldPath, value any) *WorkflowNode {
@@ -533,22 +535,22 @@ func (n *WorkflowNode) SetStaticValue(path FieldPath, value any) *WorkflowNode {
}
```
-Use this method to set static values on specified fields of Workflow nodes.
+Set static values on specified fields of Workflow nodes through this method.
-### Streaming Effects
+### Streaming Effect
-Returning to the previous "character counting" example, if our workflow's input is no longer a single message but a message stream, and our counting function can count each message chunk in the stream separately and return a "count stream":
+Going back to the previous "character counting" example, if our workflow's input is no longer a single message, but a message stream, and our counting function can count each message chunk in the stream separately and return a "count stream":
We make some modifications to the previous example:
-- Change `InvokableLambda` to `TransformableLambda`, so it can consume streams and produce streams.
-- Change the `SubStr` in the input to a static value, injected into c1 and c2.
-- Change the Workflow's overall input to `*schema.Message`.
-- Call the workflow using Transform, passing a stream containing 2 `*schema.Message`.
+- Change InvokableLambda to TransformableLambda, so it can consume streams and produce streams.
+- Change SubStr in input to static value, injected into c1 and c2.
+- Change Workflow's overall input to *schema.Message.
+- Call workflow with Transform method, passing a stream containing 2 *schema.Messages.
-The completed code:
+Completed code:
```go
// demonstrates the stream field mapping ability of eino workflow.
@@ -560,7 +562,7 @@ func main() {
}
// wordCounter is a transformable lambda function that
- // counts occurrences of SubStr within FullStr for each chunk.
+ // count occurrences of SubStr within FullStr, for each trunk.
wordCounter := func(ctx context.Context, c *schema.StreamReader[counter]) (
*schema.StreamReader[int], error) {
var subStr, cachedStr string
@@ -657,45 +659,45 @@ func main() {
}
```
-[Eino examples link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/6_stream_field_map/main.go)
+[Eino examples code link](https://github.com/cloudwego/eino-examples/blob/main/compose/workflow/6_stream_field_map/main.go)
Based on the example above, we summarize some characteristics of workflow streaming:
-- Still 100% Eino stream: four paradigms (invoke, stream, collect, transform), automatically converted, copied, concatenated, and merged by the Eino framework.
-- Field mapping configuration doesn't require special handling for streams: regardless of whether the actual input/output is a stream, the `AddInput` syntax is the same. The Eino framework handles stream-based mapping.
-- Static values don't require special handling for streams: even if the actual input is a stream, you can use `SetStaticValue` the same way. The Eino framework will place static values in the input stream, but not necessarily in the first chunk read.
+- Still 100% Eino stream: Four paradigms (invoke, stream, collect, transform), automatically converted, copied, concatenated, merged by Eino framework.
+- Field mapping configuration doesn't need special handling for streams: Whether actual input/output is a stream or not, AddInput is written the same way, Eino framework handles stream-based mapping.
+- Static values don't need special handling for streams: Even if actual input is a stream, SetStaticValue works the same way. Eino framework puts static values in input stream, but not necessarily in the first chunk read.
### Field Mapping Scenarios
#### Type Alignment
-Workflow follows the same type alignment rules as Graph, except the alignment granularity changes from complete input/output alignment to alignment between mapped field pairs. Specifically:
+Workflow follows the same type alignment rules as Graph, just the alignment granularity changes from complete input/output alignment to type alignment between paired mapped fields. Specifically:
-- Identical types: passes compile-time validation and will definitely align.
-- Different types but upstream can be assigned to downstream (e.g., upstream is a concrete type, downstream is `any`): passes compile-time validation and will definitely align.
-- Upstream cannot be assigned to downstream (e.g., upstream is `int`, downstream is `string`): compile error.
-- Upstream may be assignable to downstream (e.g., upstream is `any`, downstream is `int`): cannot be determined at compile time, deferred to runtime. At runtime, the actual upstream type is extracted and checked. If upstream cannot be assigned to downstream, an error is thrown.
+- Types completely identical, will pass Compile validation, alignment is guaranteed.
+- Types different, but upstream can be Assigned to downstream (e.g., upstream is concrete type, downstream is Any), will pass Compile validation, alignment is guaranteed.
+- Upstream cannot be Assigned to downstream (e.g., upstream is int, downstream is string), will report error at Compile.
+- Upstream may be Assignable to downstream (e.g., upstream is Any, downstream is int), cannot be determined at Compile, will be postponed to runtime, when upstream's actual type is extracted, then judge. At this point if it's determined upstream cannot be Assigned to downstream, an error will be thrown.
#### Merge Scenarios
-Merge refers to situations where a node's input is mapped from multiple `FieldMapping`s:
+Merge refers to cases where a node's input maps from multiple `FieldMappings`.
-- Mapping to multiple different fields: supported
-- Mapping to the same single field: not supported
-- Mapping to the whole input while also having mappings to specific fields: conflict, not supported
+- Mapping to multiple different fields: Supported
+- Mapping to the same field: Not supported
+- Mapping to whole, while also mapping to field: Conflict, not supported
-#### Nested `map[string]any`
+#### Nested map[string]any
-For example, this mapping: `ToFieldPath([]string{"a","b"})`, where the target node's input type is `map[string]any`, the mapping order is:
+For example this mapping: `ToFieldPath([]string{"a","b"})`, target node's input type is `map[string]any`, mapping order is:
-1. First level "a": result is `map[string]any{"a": nil}`
-2. Second level "b": result is `map[string]any{"a": map[string]any{"b": x}}`
+1. First level "a", result at this point is `map[string]any{"a": nil}`
+2. Second level "b", result at this point is `map[string]any{"a": map[string]any{"b": x}}`
-As you can see, at the second level, the Eino framework automatically replaces `any` with the actual `map[string]any`.
+As you can see, at the second level, Eino framework automatically replaced any with actual `map[string]any`
#### CustomExtractor
-In some scenarios, standard field mapping semantics cannot support the requirement. For example, if upstream is `[]int` and you want to extract the first element to map to downstream, use `WithCustomExtractor`:
+Some scenarios where standard field mapping semantics cannot support, like upstream is []int, want to extract first element to map to downstream, we use `WithCustomExtractor`:
```go
t.Run("custom extract from array element", func(t *testing.T) {
@@ -711,20 +713,20 @@ t.Run("custom extract from array element", func(t *testing.T) {
})
```
-When using `WithCustomExtractor`, all compile-time type alignment checks cannot be performed and can only be deferred to runtime validation.
+When using WithCustomExtractor, all Compile-time type alignment validation cannot be performed, can only be postponed to runtime validation.
-### Constraints
+### Some Constraints
-- Map key restrictions: only `string`, or string alias (types that can be converted to `string`).
+- Map Key restriction: Only supports string, or string alias (type that can convert to string).
- Unsupported CompileOptions:
- `WithNodeTriggerMode`, because it's fixed to `AllPredecessor`.
- - `WithMaxRunSteps`, because there are no cycles.
-- If the mapping source is a Map Key, the Map must contain this key. However, if the mapping source is a Stream, Eino cannot determine whether this key appears at least once across all frames in the stream, so validation cannot be performed for Streams.
-- If the mapping source field or target field belongs to a struct, these fields must be exported, because reflection is used internally.
-- Nil mapping source: generally supported, only errors when the mapping target cannot be nil, such as basic types (int, etc.).
+ - `WithMaxRunSteps`, because there won't be cycles.
+- If mapping source is Map Key, requires the Map to have this key. But if mapping source is Stream, Eino cannot determine if all frames in the stream have this key at least once, so cannot validate for Stream.
+- If mapping source field or target field belongs to struct, requires these fields to be exported, because reflection is used internally.
+- Mapping source is nil: Generally supported, only reports error when mapping target cannot possibly be nil, such as when target is basic type (int, etc.).
-## Real-world Usage
+## Practical Applications
### Coze-Studio Workflow
-[Coze-Studio](https://github.com/coze-dev/coze-studio) open source version's workflow engine is built on the Eino Workflow orchestration framework. See: [11. Adding New Workflow Node Types (Backend)](https://github.com/coze-dev/coze-studio/wiki/11.-%E6%96%B0%E5%A2%9E%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%8A%82%E7%82%B9%E7%B1%BB%E5%9E%8B%EF%BC%88%E5%90%8E%E7%AB%AF%EF%BC%89)
+[Coze-Studio](https://github.com/coze-dev/coze-studio) open source version's workflow engine is based on Eino Workflow orchestration framework. See: [11. Adding New Workflow Node Types (Backend)](https://github.com/coze-dev/coze-studio/wiki/11.-%E6%96%B0%E5%A2%9E%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%8A%82%E7%82%B9%E7%B1%BB%E5%9E%8B%EF%BC%88%E5%90%8E%E7%AB%AF%EF%BC%89)
diff --git a/content/en/docs/eino/core_modules/components/agentic_chat_model_guide.md b/content/en/docs/eino/core_modules/components/agentic_chat_model_guide.md
index 3724c41bbba..eec475b6961 100644
--- a/content/en/docs/eino/core_modules/components/agentic_chat_model_guide.md
+++ b/content/en/docs/eino/core_modules/components/agentic_chat_model_guide.md
@@ -1,26 +1,36 @@
---
Description: ""
-date: "2026-01-20"
+date: "2026-03-02"
lastmod: ""
tags: []
-title: 'Eino: AgenticModel Guide [Beta]'
+title: 'Eino: AgenticModel User Guide [Beta]'
weight: 10
---
-## Overview
+## Introduction
-AgenticModel is a model capability abstraction centered on "goal-driven autonomous execution". As capabilities like caching and built-in tools gain native support in advanced provider APIs such as OpenAI Responses API and Claude API, models are evolving from "one-shot Q&A engines" to "goal-oriented autonomous agents": capable of closed-loop planning, tool invocation, and iterative execution around user goals to accomplish more complex tasks.
+AgenticModel is an abstraction of model capabilities centered on "goal-driven autonomous execution". As capabilities like caching and built-in tools become natively supported in APIs from advanced providers like OpenAI Responses API and Claude API, models are evolving from "one-shot Q&A engines" to "user goal-oriented autonomous agents": capable of closed-loop planning around objectives, invoking tools, and iterative execution to accomplish more complex tasks.
### Differences from ChatModel
| AgenticModel | ChatModel |
-| Positioning | Model capability abstraction centered on "goal-driven autonomous execution", an enhanced abstraction over ChatModel | One-shot Q&A engine |
+| Positioning | Model component abstraction based on AgenticMessage | Model component abstraction based on Message |
| Core Entities | AgenticMessageContentBlock | Message |
| Capabilities | Multi-turn model conversation generationSession cachingSupport for various built-in toolsSupport for MCP toolsBetter model adaptability | Single-turn model conversation generationSession cachingSupport for simple built-in tools |
-| Related Components | AgenticModelAgenticTemplateAgenticToolsNode | ChatModelChatTemplateToolsNode |
+| Related Components | AgenticTemplateAgenticToolsNode | ChatTemplateToolsNode |
+Server-side tools (like web_search) and MCP tools are natively supported by model providers, meaning a single API request/response may contain results from multiple reasoning-action cycles. Taking web search capability as an example:
+
+- Implemented with ChatModel: requires pre-defining a custom web search tool. One reasoning-action process is as follows:
+ 1. Model generates tool call parameters
+ 2. User-side executes the tool
+ 3. Returns tool result to the model
+- Implemented with AgenticModel: can directly configure native web search tools provided by the model provider. One API request process is as follows:
+ 1. The model autonomously calls the web search tool based on the user's question and completes multiple tool calls on the model server side, generating multiple reasoning-action results until the user's task is completed.
+ 2. The user side only needs to receive the results.
+
## Component Definition
### Interface Definition
@@ -40,29 +50,29 @@ type AgenticModel interface {
#### Generate Method
-- Purpose: Generate a complete model response
+- Function: Generate a complete model response
- Parameters:
- ctx: Context object for passing request-level information and Callback Manager
- - input: List of input messages
+ - input: Input message list
- opts: Optional parameters for configuring model behavior
-- Returns:
+- Return values:
- `*schema.AgenticMessage`: The response message generated by the model
- error: Error information during generation
#### Stream Method
-- Purpose: Generate model response in streaming mode
+- Function: Generate model response in streaming mode
- Parameters: Same as Generate method
-- Returns:
+- Return values:
- `*schema.StreamReader[*schema.AgenticMessage]`: Stream reader for model response
- error: Error information during generation
#### WithTools Method
-- Purpose: Bind available tools to the model
+- Function: Bind available tools to the model
- Parameters:
- tools: List of tool information
-- Returns:
+- Return values:
- Model: A new AgenticModel instance with tools bound
- error: Error information during binding
@@ -70,7 +80,7 @@ type AgenticModel interface {
> Code location: [https://github.com/cloudwego/eino/tree/main/schema/agentic_message.go](https://github.com/cloudwego/eino/tree/main/schema/agentic_message.go)
-`AgenticMessage` is the basic unit for interacting with the model. A complete model response is encapsulated as an `AgenticMessage`, which carries complex composite content through an ordered list of `ContentBlock`. The definition is as follows:
+`AgenticMessage` is the basic unit for interacting with the model. A complete response from the model is encapsulated as an `AgenticMessage`, which carries complex composite content through an ordered set of `ContentBlock`s. The biggest difference between `AgenticMessage` and `Message` is the introduction of the `ContentBlock` array concept, which can carry multiple reasoning-action outputs from the `AgenticModel`. Definition:
```go
type AgenticMessage struct {
@@ -88,7 +98,7 @@ type AgenticMessage struct {
}
```
-`ContentBlock` is the basic building unit of `AgenticMessage`, used to carry the specific content of a message. It is designed as a polymorphic structure that identifies the type of data contained in the current block through the `Type` field and holds the corresponding non-null pointer field. `ContentBlock` enables a message to contain mixed-type rich media content or structured data, such as "text + image" or "reasoning process + tool call". The definition is as follows:
+`ContentBlock` is the basic building unit of `AgenticMessage`, used to carry the specific content of a message. It's designed as a polymorphic structure that identifies what type of data the current block contains through the `Type` field and holds the corresponding non-null pointer field. `ContentBlock` allows a message to contain mixed rich media content or structured data, such as "text + image" or "reasoning process + tool call". Definition:
```go
type ContentBlockType string
@@ -183,7 +193,7 @@ type ContentBlock struct {
}
```
-`AgenticResponseMeta` is the metadata returned in the model response, where `TokenUsage` is the metadata returned by all model providers. `OpenAIExtension`, `GeminiExtension`, and `ClaudeExtension` are extension field definitions specific to OpenAI, Gemini, and Claude models respectively; extension information from other model providers is placed in `Extension`, with specific definitions provided by the corresponding component implementations in **eino-ext**.
+`AgenticResponseMeta` is the metadata returned in model responses, where `TokenUsage` is common metadata returned by all model providers. `OpenAIExtension`, `GeminiExtension`, and `ClaudeExtension` are extension field definitions specific to OpenAI, Gemini, and Claude models respectively; extension information from other model providers is placed in `Extension`, with specific definitions provided by the corresponding component implementations in **eino-ext**.
```go
type AgenticResponseMeta struct {
@@ -206,7 +216,7 @@ type AgenticResponseMeta struct {
#### Reasoning
-The Reasoning type is used to represent the model's reasoning process and thinking content. Some advanced models can perform internal reasoning before generating the final answer, and this reasoning content can be passed through this type.
+The Reasoning type represents the model's reasoning process and thinking content. Some advanced models can perform internal reasoning before generating the final answer, and this reasoning content can be conveyed through this type.
- Definition
@@ -232,7 +242,7 @@ reasoning := &schema.Reasoning{
#### UserInputText
-UserInputText is the most basic content type, used to pass plain text input. It is the primary way for users to interact with the model, suitable for natural language conversations, instruction delivery, and question asking.
+UserInputText is the most basic content type for conveying plain text input. It's the primary way users interact with models, suitable for natural language dialogue, instruction delivery, and question asking.
- Definition
@@ -247,18 +257,18 @@ type UserInputText struct {
```go
textInput := &schema.UserInputText{
- Text: "Please help me analyze the performance bottlenecks in this code",
+ Text: "Please help me analyze the performance bottleneck of this code",
}
// Or use convenience functions to create messages
-textInput := schema.UserAgenticMessage("Please help me analyze the performance bottlenecks in this code")
+textInput := schema.UserAgenticMessage("Please help me analyze the performance bottleneck of this code")
textInput := schema.SystemAgenticMessage("You are an intelligent assistant")
textInput := schema.DeveloperAgenticMessage("You are an intelligent assistant")
```
#### UserInputImage
-UserInputImage is used to provide image content to the model. It supports passing image data via URL reference or Base64 encoding, suitable for visual understanding, image analysis, and multimodal conversations.
+UserInputImage is used to provide image content to the model. It supports passing image data via URL reference or Base64 encoding, suitable for visual understanding, image analysis, and multimodal dialogue scenarios.
- Definition
@@ -297,7 +307,7 @@ imageInput := &schema.UserInputImage{
#### UserInputAudio
-UserInputAudio is used to provide audio content to the model. It is suitable for speech recognition, audio analysis, and multimodal understanding.
+UserInputAudio is used to provide audio content to the model. It's suitable for speech recognition, audio analysis, and multimodal understanding scenarios.
- Definition
@@ -325,7 +335,7 @@ audioInput := &schema.UserInputAudio{
#### UserInputVideo
-UserInputVideo is used to provide video content to the model. It is suitable for video understanding, scene analysis, and action recognition and other advanced visual tasks.
+UserInputVideo is used to provide video content to the model. It's suitable for video understanding, scene analysis, and action recognition advanced visual tasks.
- Definition
@@ -353,7 +363,7 @@ videoInput := &schema.UserInputVideo{
#### UserInputFile
-UserInputFile is used to provide file content to the model. It is suitable for document analysis, data extraction, and knowledge understanding.
+UserInputFile is used to provide file content to the model. It's suitable for document analysis, data extraction, and knowledge understanding scenarios.
- Definition
@@ -385,7 +395,7 @@ fileInput := &schema.UserInputFile{
#### AssistantGenText
-AssistantGenText is the text content generated by the model, which is the most common form of model output. For different model providers, the extension field definitions vary: OpenAI models use `OpenAIExtension`, Claude models use `ClaudeExtension`; extension information from other model providers is placed in `Extension`, with specific definitions provided by the corresponding component implementations in **eino-ext**.
+AssistantGenText is the text content generated by the model, the most common form of model output. Extension field definitions differ for different model providers: OpenAI models use `OpenAIExtension`, Claude models use `ClaudeExtension`; extension information from other model providers is placed in `Extension`, with specific definitions provided by corresponding component implementations in **eino-ext**.
- Definition
@@ -430,13 +440,13 @@ type AssistantGenText struct {
"github.com/cloudwego/eino-ext/components/model/agenticark"
)
- // Assert to specific implementation definition
+ // Assert to concrete implementation definition
ext := textGen.Extension.(*agenticark.AssistantGenTextExtension)
```
#### AssistantGenImage
-AssistantGenImage is the image content generated by the model. Some models have image generation capabilities and can create images based on text descriptions, with the output passed through this type.
+AssistantGenImage is the image content generated by the model. Some models have image generation capabilities and can create images based on text descriptions, with output results conveyed through this type.
- Definition
@@ -464,7 +474,7 @@ imageGen := &schema.AssistantGenImage{
#### AssistantGenAudio
-AssistantGenAudio is the audio content generated by the model. Some models have audio generation capabilities, and the output audio data is passed through this type.
+AssistantGenAudio is the audio content generated by the model. Some models have audio generation capabilities, with output audio data conveyed through this type.
- Definition
@@ -492,7 +502,7 @@ audioGen := &schema.AssistantGenAudio{
#### AssistantGenVideo
-AssistantGenVideo is the video content generated by the model. Some models have video generation capabilities, and the output video data is passed through this type.
+AssistantGenVideo is the video content generated by the model. Some models have video generation capabilities, with output video data conveyed through this type.
- Definition
@@ -520,7 +530,7 @@ audioGen := &schema.AssistantGenAudio{
#### FunctionToolCall
-FunctionToolCall represents a user-defined function tool call initiated by the model. When the model needs to perform a specific function, it generates a tool call request containing the tool name and parameters, with the actual execution handled by the user side.
+FunctionToolCall represents a user-defined function tool call initiated by the model. When the model needs to execute a specific function, it generates a tool call request containing the tool name and parameters, with actual execution handled by the user side.
- Definition
@@ -549,7 +559,7 @@ toolCall := &schema.FunctionToolCall{
#### FunctionToolResult
-FunctionToolResult represents the execution result of a user-defined function tool. After the user side executes the tool call, the result is returned to the model through this type, allowing the model to continue generating responses.
+FunctionToolResult represents the execution result of a user-defined function tool. After the user side executes a tool call, the result is returned to the model through this type, allowing the model to continue generating responses.
- Definition
@@ -575,7 +585,7 @@ toolResult := &schema.FunctionToolResult{
Result: `{"temperature": 15, "condition": "sunny"}`,
}
-// Or use convenience functions to create messages
+// Or use convenience function to create message
msg := schema.FunctionToolResultAgenticMessage(
"call_abc123",
"get_weather",
@@ -585,7 +595,7 @@ msg := schema.FunctionToolResultAgenticMessage(
#### ServerToolCall
-ServerToolCall represents a call to a model server's built-in tool. Some model providers integrate specific tools on the server side (such as web search, code executor), which the model can call autonomously without user intervention. `Arguments` is the parameters for the model to call the server-side built-in tool, with specific definitions provided by the corresponding component implementations in **eino-ext**.
+ServerToolCall represents the invocation of a model server-side built-in tool. Some model providers integrate specific tools (like web search, code executor) on the server side, and the model can autonomously call these tools without user intervention. `Arguments` are the parameters for the model to call the server-side built-in tool, with specific definitions provided by corresponding component implementations in **eino-ext**.
- Definition
@@ -617,7 +627,7 @@ type ServerToolCall struct {
WebSearch: &WebSearchArguments{
ActionType: WebSearchActionSearch,
Search: &WebSearchQuery{
- Query: "weather in Beijing today",
+ Query: "Beijing weather today",
},
},
},
@@ -631,13 +641,13 @@ type ServerToolCall struct {
"github.com/cloudwego/eino-ext/components/model/agenticopenai"
)
- // Assert to specific implementation definition
+ // Assert to concrete implementation definition
args := serverCall.Arguments.(*agenticopenai.ServerToolCallArguments)
```
#### ServerToolResult
-ServerToolResult represents the execution result of a server-side built-in tool. After the model server executes the tool call, the result is returned through this type. `Result` is the result of the model calling the server-side built-in tool, with specific definitions provided by the corresponding component implementations in **eino-ext**.
+ServerToolResult represents the execution result of a server-side built-in tool. After the model server executes a tool call, the result is returned through this type. `Result` is the result of the model calling the server-side built-in tool, with specific definitions provided by corresponding component implementations in **eino-ext**.
- Definition
@@ -683,7 +693,7 @@ type ServerToolResult struct {
"github.com/cloudwego/eino-ext/components/model/agenticopenai"
)
- // Assert to specific implementation definition
+ // Assert to concrete implementation definition
args := serverResult.Result.(*agenticopenai.ServerToolResult)
```
@@ -725,7 +735,7 @@ mcpCall := &schema.MCPToolCall{
#### MCPToolResult
-MCPToolResult represents the MCP tool execution result returned by the model. After the model autonomously completes the MCP tool call, the result or error information is returned through this type.
+MCPToolResult represents the MCP tool execution result returned by the model. After the model autonomously completes an MCP tool call, the result or error information is returned through this type.
- Definition
@@ -759,7 +769,7 @@ type MCPToolCallError struct {
- Example
```go
-// MCP tool call success
+// MCP tool call succeeded
mcpResult := &schema.MCPToolResult{
ServerLabel: "database-server",
CallID: "mcp_call_456",
@@ -767,7 +777,7 @@ mcpResult := &schema.MCPToolResult{
Result: `{"rows": [...], "count": 10}`,
}
-// MCP tool call failure
+// MCP tool call failed
errorCode := int64(500)
mcpError := &schema.MCPToolResult{
ServerLabel: "database-server",
@@ -782,7 +792,7 @@ mcpError := &schema.MCPToolResult{
#### MCPListToolsResult
-MCPListToolsResult represents the query result of available tools from an MCP server returned by the model. Models that support configuring MCP tools can autonomously send available tool list query requests to the MCP server, and the query results are returned through this type.
+MCPListToolsResult represents the query result for available tools from an MCP server returned by the model. Models that support configuring MCP tools can autonomously query the MCP server for available tools, with the query result returned through this type.
- Definition
@@ -823,7 +833,7 @@ toolsList := &schema.MCPListToolsResult{
},
{
Name: "create_table",
- Description: "Create data table",
+ Description: "Create database table",
InputSchema: &jsonschema.Schema{...},
},
},
@@ -832,7 +842,7 @@ toolsList := &schema.MCPListToolsResult{
#### MCPToolApprovalRequest
-MCPToolApprovalRequest represents an MCP tool call request that requires user approval. In the model's autonomous MCP tool calling process, certain sensitive or high-risk operations (such as data deletion, external payments, etc.) require explicit user authorization before execution. Some models support configuring MCP tool call approval policies, and before each call to a high-risk MCP tool, the model returns a call authorization request through this type.
+MCPToolApprovalRequest represents an MCP tool call request that requires user approval. In the model's autonomous MCP tool calling process, certain sensitive or high-risk operations (like data deletion, external payments) require explicit user authorization before execution. Some models support configuring MCP tool call approval policies, and before each high-risk MCP tool call, the model returns an authorization request through this type.
- Definition
@@ -865,7 +875,7 @@ approvalReq := &schema.MCPToolApprovalRequest{
#### MCPToolApprovalResponse
-MCPToolApprovalResponse represents the user's approval decision for an MCP tool call. After receiving an MCPToolApprovalRequest, the user needs to review the operation details and make a decision. The user can choose to approve or reject the operation and optionally provide a reason for the decision.
+MCPToolApprovalResponse represents the user's approval decision for an MCP tool call. After receiving an MCPToolApprovalRequest, the user needs to review the operation details and make a decision, choosing to approve or reject the operation, with an optional reason for the decision.
- Definition
@@ -895,7 +905,7 @@ approvalResp := &schema.MCPToolApprovalResponse{
#### StreamingMeta
-StreamingMeta is used in streaming response scenarios to identify the position of a content block in the final response. During streaming generation, content may be returned in multiple blocks incrementally, and the index allows for correct assembly of the complete response.
+StreamingMeta is used in streaming response scenarios to identify the position of a content block in the final response. During streaming generation, content may be returned in multiple chunks, and the index allows for correctly assembling the complete response.
- Definition
@@ -916,7 +926,7 @@ block := schema.NewContentBlockChunk(textGen, meta)
### Common Options
-AgenticModel and ChatModel share a common set of Options for configuring model behavior. Additionally, AgenticModel provides some exclusive configuration options specific to itself.
+AgenticModel and ChatModel share a common set of Options for configuring model behavior. Additionally, AgenticModel provides some exclusive configuration options.
> Code location: [https://github.com/cloudwego/eino/tree/main/components/model/option.go](https://github.com/cloudwego/eino/tree/main/components/model/option.go)
@@ -933,16 +943,16 @@ AgenticModel and ChatModel share a common set of Options for configuring model b
| AllowedTools | Supported | Not Supported |
-Accordingly, AgenticModel adds the following method for setting Options:
+Correspondingly, AgenticModel has added the following method for setting Options:
```go
// WithAgenticToolChoice is the option to set tool choice for the agentic model.
func WithAgenticToolChoice(toolChoice schema.ToolChoice, allowedTools ...*schema.AllowedTool) Option {}
```
-#### Component Implementation Custom Options
+#### Implementation-Specific Custom Options
-The WrapImplSpecificOptFn method provides the ability for component implementations to inject custom Options. Developers need to define proprietary Option types in specific implementations and provide corresponding Option configuration methods.
+The WrapImplSpecificOptFn method provides component implementations the ability to inject custom Options. Developers need to define implementation-specific Option types and provide corresponding Option configuration methods.
```go
type openaiOptions struct {
@@ -1101,9 +1111,9 @@ func main() {
}
```
-## Options and Callbacks Usage
+## Option and Callback Usage
-### Options Usage
+### Option Usage
```go
import "github.com/cloudwego/eino/components/model"
diff --git a/content/en/docs/eino/core_modules/components/chat_model_guide.md b/content/en/docs/eino/core_modules/components/chat_model_guide.md
index 30aae23c4e1..709e8f47505 100644
--- a/content/en/docs/eino/core_modules/components/chat_model_guide.md
+++ b/content/en/docs/eino/core_modules/components/chat_model_guide.md
@@ -3,18 +3,18 @@ Description: ""
date: "2026-01-20"
lastmod: ""
tags: []
-title: 'Eino: ChatModel Guide'
-weight: 1
+title: 'Eino: ChatModel User Guide'
+weight: 8
---
-## Overview
+## Introduction
-The Model component is used to interact with large language models. Its main purpose is to send user input messages to the language model and obtain the model's response. This component plays an important role in the following scenarios:
+The Model component is a component for interacting with large language models. Its main purpose is to send user input messages to the language model and obtain the model's response. This component plays an important role in the following scenarios:
-- Natural language conversations
+- Natural language dialogue
- Text generation and completion
-- Tool call parameter generation
-- Multimodal interactions (text, images, audio, etc.)
+- Parameter generation for tool calls
+- Multimodal interaction (text, images, audio, etc.)
## Component Definition
@@ -42,8 +42,8 @@ type ToolCallingChatModel interface {
- Function: Generate a complete model response
- Parameters:
- - ctx: Context object for passing request-level information, also used to pass the Callback Manager
- - input: List of input messages
+ - ctx: Context object for passing request-level information and Callback Manager
+ - input: Input message list
- opts: Optional parameters for configuring model behavior
- Return values:
- `*schema.Message`: The response message generated by the model
@@ -52,7 +52,7 @@ type ToolCallingChatModel interface {
#### Stream Method
- Function: Generate model response in streaming mode
-- Parameters: Same as the Generate method
+- Parameters: Same as Generate method
- Return values:
- `*schema.StreamReader[*schema.Message]`: Stream reader for model response
- error: Error information during generation
@@ -72,7 +72,7 @@ type ToolCallingChatModel interface {
```go
type Message struct {
- // Role indicates the role of the message (system/user/assistant/tool)
+ // Role represents the message role (system/user/assistant/tool)
Role RoleType
// Content is the text content of the message
Content string
@@ -82,18 +82,18 @@ type Message struct {
// UserInputMultiContent stores user input multimodal data, supporting text, images, audio, video, files
// When using this field, the model role is restricted to User
UserInputMultiContent []MessageInputPart
- // AssistantGenMultiContent holds multimodal data output by the model, supporting text, images, audio, video
+ // AssistantGenMultiContent stores model output multimodal data, supporting text, images, audio, video
// When using this field, the model role is restricted to Assistant
AssistantGenMultiContent []MessageOutputPart
// Name is the sender name of the message
Name string
// ToolCalls is the tool call information in assistant messages
ToolCalls []ToolCall
- // ToolCallID is the tool call ID for tool messages
+ // ToolCallID is the tool call ID of tool messages
ToolCallID string
// ResponseMeta contains response metadata
ResponseMeta *ResponseMeta
- // Extra is used to store additional information
+ // Extra stores additional information
Extra map[string]any
}
```
@@ -102,8 +102,8 @@ The Message struct is the basic structure for model interaction, supporting:
- Multiple roles: system, user, assistant (ai), tool
- Multimodal content: text, images, audio, video, files
-- Tool calls: Support for model calling external tools and functions
-- Metadata: Including response reason, token usage statistics, etc.
+- Tool calls: supports model calling external tools and functions
+- Metadata: includes response reason, token usage statistics, etc.
### Common Options
@@ -121,12 +121,12 @@ type Options struct {
Model *string
// TopP controls the diversity of output
TopP *float32
- // Stop specifies the conditions to stop generation
+ // Stop specifies conditions to stop generation
Stop []string
}
```
-Options can be set using the following methods:
+Options can be set in the following ways:
```go
// Set temperature
@@ -183,7 +183,7 @@ import (
"github.com/cloudwego/eino/schema"
)
-// Initialize model (using OpenAI as an example)
+// Initialize model (using openai as example)
cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
// Configuration parameters
})
@@ -192,11 +192,11 @@ cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
messages := []*schema.Message{
{
Role: schema.System,
- Content: "你是一个有帮助的助手。",
+ Content: "You are a helpful assistant.",
},
{
Role: schema.User,
- Content: "你好!",
+ Content: "Hello!",
},
}
@@ -282,7 +282,7 @@ handler := &callbacksHelper.ModelCallbackHandler{
return ctx
},
OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *model.CallbackOutput) context.Context {
- fmt.Printf("Generation complete, Token usage: %+v\n", output.TokenUsage)
+ fmt.Printf("Generation complete, token usage: %+v\n", output.TokenUsage)
return ctx
},
OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*model.CallbackOutput]) context.Context {
@@ -336,22 +336,22 @@ result, err := runnable.Invoke(ctx, messages, compose.WithCallbacks(helper))
## **Existing Implementations**
-1. OpenAI ChatModel: Using OpenAI's GPT series models [ChatModel - OpenAI](/docs/eino/ecosystem_integration/chat_model/chat_model_openai)
-2. Ollama ChatModel: Using Ollama local models [ChatModel - Ollama](/docs/eino/ecosystem_integration/chat_model/chat_model_ollama)
-3. ARK ChatModel: Using ARK platform model services [ChatModel - ARK](/docs/eino/ecosystem_integration/chat_model/chat_model_ark)
+1. OpenAI ChatModel: Using OpenAI's GPT series models [ChatModel - OpenAI](https://bytedance.larkoffice.com/wiki/NguEw85n6iJjShkVtdQcHpydnld)
+2. Ollama ChatModel: Using Ollama local models [ChatModel - Ollama](https://bytedance.larkoffice.com/wiki/WWngw1XMViwgyYkNuZgcjZnxnke)
+3. ARK ChatModel: Using ARK platform model services [ChatModel - ARK](https://bytedance.larkoffice.com/wiki/WUzzwaX8ricGwZk1i1mcJHHNnEl)
4. More: [Eino ChatModel](https://www.cloudwego.io/docs/eino/ecosystem_integration/chat_model/)
-## Custom Implementation Reference
+## Implementation Reference
-When implementing a custom ChatModel component, pay attention to the following points:
+When implementing a custom ChatModel component, note the following:
1. Make sure to implement common options
-2. Make sure to implement the callback mechanism
-3. Remember to close the writer after streaming output is complete
+2. Make sure to implement callback mechanism
+3. Remember to close the writer after completing output in streaming mode
### Option Mechanism
-If a custom ChatModel needs Options beyond the common Options, you can use the component abstraction utility functions to implement custom Options, for example:
+Custom ChatModel can use the component abstraction utility function to implement custom Options if Options beyond common Options are needed, for example:
```go
import (
@@ -445,7 +445,7 @@ func NewMyChatModel(config *MyChatModelConfig) (*MyChatModel, error) {
}
func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) {
- // 1. Process options
+ // 1. Handle options
options := &MyChatModelOptions{
Options: &model.Options{
Model: &m.model,
@@ -456,7 +456,7 @@ func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message,
options.Options = model.GetCommonOptions(options.Options, opts...)
options = model.GetImplSpecificOptions(options, opts...)
- // 2. Callback before starting generation
+ // 2. Callback before generation starts
ctx = callbacks.OnStart(ctx, &model.CallbackInput{
Messages: messages,
Config: &model.Config{
@@ -467,7 +467,7 @@ func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message,
// 3. Execute generation logic
response, err := m.doGenerate(ctx, messages, options)
- // 4. Handle error and completion callbacks
+ // 4. Handle errors and completion callback
if err != nil {
ctx = callbacks.OnError(ctx, err)
return nil, err
@@ -481,7 +481,7 @@ func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message,
}
func (m *MyChatModel) Stream(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) {
- // 1. Process options
+ // 1. Handle options
options := &MyChatModelOptions{
Options: &model.Options{
Model: &m.model,
@@ -492,7 +492,7 @@ func (m *MyChatModel) Stream(ctx context.Context, messages []*schema.Message, op
options.Options = model.GetCommonOptions(options.Options, opts...)
options = model.GetImplSpecificOptions(options, opts...)
- // 2. Callback before starting streaming generation
+ // 2. Callback before streaming generation starts
ctx = callbacks.OnStart(ctx, &model.CallbackInput{
Messages: messages,
Config: &model.Config{
@@ -501,18 +501,18 @@ func (m *MyChatModel) Stream(ctx context.Context, messages []*schema.Message, op
})
// 3. Create streaming response
- // Pipe produces a StreamReader and a StreamWriter; writing to StreamWriter can be read from StreamReader, both are concurrency-safe.
- // The implementation asynchronously writes generated content to StreamWriter and returns StreamReader as the return value
- // ***StreamReader is a data stream that can only be read once. When implementing Callback yourself, you need to pass the data stream to callback via OnEndWithCallbackOutput and also return a data stream, requiring a copy of the data stream
- // Considering this scenario always requires copying the data stream, the OnEndWithStreamOutput function will copy internally and return an unread stream
- // The following code demonstrates one stream processing approach; the processing method is not unique
+ // Pipe produces a StreamReader and StreamWriter. Writing to StreamWriter can be read from StreamReader, both are concurrency-safe.
+ // In implementation, write generated content to StreamWriter asynchronously and return StreamReader as return value
+ // ***StreamReader is a data stream that can only be read once. When implementing Callback yourself, you need to pass the data stream to callback via OnEndWithCallbackOutput and also return a data stream, requiring copying the data stream.
+ // Considering this scenario always requires copying the data stream, the OnEndWithCallbackOutput function copies internally and returns an unread stream.
+ // The following code demonstrates one stream handling approach; handling approaches are not unique.
sr, sw := schema.Pipe[*model.CallbackOutput](1)
- // 4. Start asynchronous generation
+ // 4. Start async generation
go func() {
defer sw.Close()
- // Stream writing
+ // Stream write
m.doStream(ctx, messages, options, sw)
}()
diff --git a/content/en/docs/eino/core_modules/components/chat_template_guide.md b/content/en/docs/eino/core_modules/components/chat_template_guide.md
index 5c793b1580b..1549638ea12 100644
--- a/content/en/docs/eino/core_modules/components/chat_template_guide.md
+++ b/content/en/docs/eino/core_modules/components/chat_template_guide.md
@@ -3,23 +3,23 @@ Description: ""
date: "2025-11-20"
lastmod: ""
tags: []
-title: 'Eino: ChatTemplate Guide'
-weight: 2
+title: 'Eino: ChatTemplate User Guide'
+weight: 7
---
-## Introduction
+## **Introduction**
-The `Prompt` component formats message templates by filling user-provided variables into predefined message structures. It’s used to generate standardized messages for model interaction and is useful for:
+The Prompt component is a component for processing and formatting prompt templates. Its main purpose is to fill user-provided variable values into predefined message templates to generate standard message formats for interacting with language models. This component can be used in the following scenarios:
-- Structured system prompts
-- Multi-turn dialogue templates (including history)
-- Reusable prompt patterns
+- Building structured system prompts
+- Handling multi-turn dialogue templates (including history)
+- Implementing reusable prompt patterns
-## Component Definition
+## **Component Definition**
-### Interface
+### **Interface Definition**
-> Code: `eino/components/prompt/interface.go`
+> Code location: eino/components/prompt/interface.go
```go
type ChatTemplate interface {
@@ -27,52 +27,62 @@ type ChatTemplate interface {
}
```
-#### Format
+#### **Format Method**
-- Purpose: fill variables into the message template
-- Params:
- - `ctx`: request-scoped info and callback manager
- - `vs`: variables map used to fill placeholders
- - `opts`: optional formatting controls
-- Returns:
- - `[]*schema.Message`: formatted messages
- - `error`
+- Function: Fill variable values into message templates
+- Parameters:
+ - ctx: Context object for passing request-level information and Callback Manager
+ - vs: Variable value mapping for filling template placeholders
+ - opts: Optional parameters for configuring formatting behavior
+- Return values:
+ - `[]*schema.Message`: Formatted message list
+ - error: Error information during formatting
-### Built-in Templating
+### **Built-in Templating Methods**
-Prompt supports three built-in templating modes:
+The Prompt component has built-in support for three templating methods:
-1. `FString` (`schema.FString`)
- - `{variable}` syntax for substitution
- - Simple and direct for basic text replacement
- - Example: `"You are a {role}. Please help me {task}."`
-2. `GoTemplate` (`schema.GoTemplate`)
- - Go `text/template` syntax
- - Supports conditionals, loops, etc.
+1. FString format (schema.FString)
+
+ - Uses `{variable}` syntax for variable substitution
+ - Simple and intuitive, suitable for basic text replacement scenarios
+ - Example: `"You are a {role}, please help me {task}."`
+2. GoTemplate format (schema.GoTemplate)
+
+ - Uses Go standard library text/template syntax
+ - Supports conditional logic, loops, and other complex logic
- Example: `"{{if .expert}}As an expert{{end}} please {{.action}}"`
-3. `Jinja2` (`schema.Jinja2`)
- - Jinja2 template syntax
+3. Jinja2 format (schema.Jinja2)
+
+ - Uses Jinja2 template syntax
- Example: `"{% if level == 'expert' %}From an expert perspective{% endif %} analyze {{topic}}"`
-### Options
+### **Common Options**
-Prompt includes an `Option` mechanism; there’s no global option abstraction. Each implementation may define its own specific options and wrap them via `WrapImplSpecificOptFn`.
+The Prompt component uses Option to define optional parameters. ChatTemplate has no common option abstraction. Each specific implementation can define its own specific Options, wrapped into a unified Option type through the WrapImplSpecificOptFn function.
-## Usage
+## **Usage**
-`ChatTemplate` is typically used before `ChatModel` to prepare context.
+ChatTemplate is generally used before ChatModel for context preparation.
### Creation Methods
-- `prompt.FromMessages()` — compose multiple messages into a template.
-- `schema.Message{}` — since `Message` implements `Format`, you can use it directly as a template.
-- `schema.SystemMessage()` — create a system-role message.
-- `schema.AssistantMessage()` — create an assistant-role message.
-- `schema.UserMessage()` — create a user-role message.
-- `schema.ToolMessage()` — create a tool-role message.
-- `schema.MessagesPlaceholder()` — insert a `[]*schema.Message` (e.g., history) into the message list.
-
-### Standalone Usage
+- `prompt.FromMessages()`
+ - Used to turn multiple messages into a chat template.
+- `schema.Message{}`
+ - schema.Message is a struct that implements the Format interface, so you can directly construct `schema.Message{}` as a template
+- `schema.SystemMessage()`
+ - This method is a shortcut for constructing a message with role "system"
+- `schema.AssistantMessage()`
+ - This method is a shortcut for constructing a message with role "assistant"
+- `schema.UserMessage()`
+ - This method is a shortcut for constructing a message with role "user"
+- `schema.ToolMessage()`
+ - This method is a shortcut for constructing a message with role "tool"
+- `schema.MessagesPlaceholder()`
+ - Can be used to insert a `[]*schema.Message` into the message list, commonly used for inserting conversation history
+
+### **Standalone Usage**
```go
import (
@@ -82,23 +92,26 @@ import (
// Create template
template := prompt.FromMessages(schema.FString,
- schema.SystemMessage("你是一个{role}。"),
+ schema.SystemMessage("You are a {role}."),
schema.MessagesPlaceholder("history_key", false),
- &schema.Message{ Role: schema.User, Content: "请帮我{task}。" },
+ &schema.Message{
+ Role: schema.User,
+ Content: "Please help me {task}.",
+ },
)
-// Variables
+// Prepare variables
variables := map[string]any{
- "role": "专业的助手",
- "task": "写一首诗",
- "history_key": []*schema.Message{{Role: schema.User, Content: "告诉我油画是什么?"}, {Role: schema.Assistant, Content: "油画是xxx"}},
+ "role": "professional assistant",
+ "task": "write a poem",
+ "history_key": []*schema.Message{{Role: schema.User, Content: "Tell me what is oil painting?"}, {Role: schema.Assistant, Content: "Oil painting is xxx"}},
}
-// Format
+// Format template
messages, err := template.Format(context.Background(), variables)
```
-### In Orchestration
+### **Usage in Orchestration**
```go
import (
@@ -123,22 +136,24 @@ graph := compose.NewGraph[map[string]any, []*schema.Message]()
graph.AddChatTemplateNode("template_node", template)
```
-### Pull Data from a Predecessor Node
+### Getting Data from Predecessor Node Output
-Use `WithOutputKey` to map a node’s output into a keyed `map[string]any`:
+When using AddNode, you can add the WithOutputKey Option to convert the node's output to a Map:
```go
+// This node's output will be changed from string to map[string]any,
+// and the map will have only one element with key "your_output_key" and value being the actual string output from the node
graph.AddLambdaNode("your_node_key", compose.InvokableLambda(func(ctx context.Context, input []*schema.Message) (str string, err error) {
// your logic
return
}), compose.WithOutputKey("your_output_key"))
```
-Then refer to that key within a downstream `ChatTemplate` node.
+After converting the predecessor node's output to map[string]any and setting the key, use the value corresponding to that key in the subsequent ChatTemplate node.
-## Options and Callbacks
+## **Option and Callback Usage**
-### Callback Example
+### **Callback Usage Example**
```go
import (
@@ -150,55 +165,70 @@ import (
"github.com/cloudwego/eino/components/prompt"
)
+// Create callback handler
handler := &callbackHelper.PromptCallbackHandler{
OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *prompt.CallbackInput) context.Context {
- fmt.Printf("Formatting template; variables: %v\n", input.Variables)
+ fmt.Printf("Starting template formatting, variables: %v\n", input.Variables)
return ctx
},
OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *prompt.CallbackOutput) context.Context {
- fmt.Printf("Template formatted; messages: %d\n", len(output.Result))
+ fmt.Printf("Template formatting complete, generated message count: %d\n", len(output.Result))
return ctx
},
}
+// Use callback handler
helper := callbackHelper.NewHandlerHelper().
Prompt(handler).
Handler()
+// Use at runtime
runnable, err := chain.Compile()
+if err != nil {
+ return err
+}
result, err := runnable.Invoke(ctx, variables, compose.WithCallbacks(helper))
```
-## Implementation Notes
+## **Implementation Reference**
-### Option Mechanism
+### Option **Mechanism**
-If needed, define custom prompt options:
+If needed, component implementers can implement custom prompt options:
```go
import (
"github.com/cloudwego/eino/components/prompt"
)
+// Define Option struct
type MyPromptOptions struct {
StrictMode bool
DefaultValues map[string]string
}
+// Define Option functions
func WithStrictMode(strict bool) prompt.Option {
- return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) { o.StrictMode = strict })
+ return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) {
+ o.StrictMode = strict
+ })
}
func WithDefaultValues(values map[string]string) prompt.Option {
- return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) { o.DefaultValues = values })
+ return prompt.WrapImplSpecificOptFn(func(o *MyPromptOptions) {
+ o.DefaultValues = values
+ })
}
```
-### Callback Structures
+### **Callback Handling**
+
+Prompt implementations need to trigger callbacks at appropriate times. The following structures are defined by the component:
-> Code: `eino/components/prompt/callback_extra.go`
+> Code location: eino/components/prompt/callback_extra.go
```go
+// Define callback input and output
type CallbackInput struct {
Variables map[string]any
Templates []schema.MessagesTemplate
@@ -212,7 +242,7 @@ type CallbackOutput struct {
}
```
-### Complete Implementation Example
+### **Complete Implementation Example**
```go
type MyPrompt struct {
@@ -232,30 +262,41 @@ func NewMyPrompt(config *MyPromptConfig) (*MyPrompt, error) {
}
func (p *MyPrompt) Format(ctx context.Context, vs map[string]any, opts ...prompt.Option) ([]*schema.Message, error) {
+ // 1. Handle Options
options := &MyPromptOptions{
StrictMode: p.strictMode,
DefaultValues: p.defaultValues,
}
options = prompt.GetImplSpecificOptions(options, opts...)
+
+ // 2. Get callback manager
cm := callbacks.ManagerFromContext(ctx)
+
+ // 3. Callback before formatting starts
ctx = cm.OnStart(ctx, info, &prompt.CallbackInput{
Variables: vs,
Templates: p.templates,
})
+
+ // 4. Execute formatting logic
messages, err := p.doFormat(ctx, vs, options)
+
+ // 5. Handle errors and completion callback
if err != nil {
ctx = cm.OnError(ctx, info, err)
return nil, err
}
+
ctx = cm.OnEnd(ctx, info, &prompt.CallbackOutput{
Result: messages,
Templates: p.templates,
})
+
return messages, nil
}
func (p *MyPrompt) doFormat(ctx context.Context, vs map[string]any, opts *MyPromptOptions) ([]*schema.Message, error) {
- // 实现自己定义逻辑
+ // Implement your own defined logic
return messages, nil
}
```
diff --git a/content/en/docs/eino/core_modules/components/document_loader_guide/_index.md b/content/en/docs/eino/core_modules/components/document_loader_guide/_index.md
index a997debf945..9302eef06c9 100644
--- a/content/en/docs/eino/core_modules/components/document_loader_guide/_index.md
+++ b/content/en/docs/eino/core_modules/components/document_loader_guide/_index.md
@@ -3,22 +3,22 @@ Description: ""
date: "2025-11-20"
lastmod: ""
tags: []
-title: 'Eino: Document Loader Guide'
-weight: 8
+title: 'Eino: Document Loader User Guide'
+weight: 1
---
-## Introduction
+## **Introduction**
-`Document Loader` loads documents from various sources (e.g., web URLs, local files) and converts them into a standard document format. It’s useful for scenarios such as:
+Document Loader is a component for loading documents. Its main purpose is to load document content from different sources (such as network URLs, local files, etc.) and convert them into a standard document format. This component plays an important role in scenarios that require obtaining document content from various sources, such as:
-- Loading web content from URLs
-- Reading local documents like PDF or Word
+- Loading web content from network URLs
+- Reading local PDF, Word, and other format documents
-## Component Definition
+## **Component Definition**
-### Interface
+### **Interface Definition**
-> Code: `eino/components/document/interface.go`
+> Code location: eino/components/document/interface.go
```go
type Loader interface {
@@ -26,18 +26,18 @@ type Loader interface {
}
```
-#### Load
+#### **Load Method**
-- Purpose: load documents from a given source
-- Params:
- - `ctx`: request context and callback manager
- - `src`: document source including URI
- - `opts`: loader options
-- Returns:
- - `[]*schema.Document`: loaded documents
- - `error`
+- Function: Load documents from specified data sources
+- Parameters:
+ - ctx: Context object for passing request-level information and Callback Manager
+ - src: Document source containing URI information of the document
+ - opts: Loading options for configuring loading behavior
+- Return values:
+ - `[]*schema.Document`: Loaded document list
+ - error: Error information during loading
-### Source
+### **Source Struct**
```go
type Source struct {
@@ -45,35 +45,43 @@ type Source struct {
}
```
-Defines source information:
+The Source struct defines the document source information:
-- `URI`: a web URL or local file path
+- URI: Uniform Resource Identifier of the document, can be a network URL or local file path
-### Document
+### **Document Struct**
```go
type Document struct {
+ // ID is the unique identifier of the document
ID string
+ // Content is the content of the document
Content string
+ // MetaData stores metadata information of the document
MetaData map[string]any
}
```
-Standard document fields:
+The Document struct is the standard format for documents, containing the following important fields:
-- `ID`: unique identifier
-- `Content`: document content
-- `MetaData`: source info, embeddings, scores, sub-indexes, and custom metadata
+- ID: Unique identifier of the document, used to uniquely identify a document in the system
+- Content: Actual content of the document
+- MetaData: Metadata of the document, can store information such as:
+ - Source information of the document
+ - Vector representation of the document (for vector retrieval)
+ - Document score (for ranking)
+ - Document sub-index (for hierarchical retrieval)
+ - Other custom metadata
-### Options
+### **Common Options**
-`LoaderOption` represents loader options. There is no global common option; each implementation defines its own and wraps via `WrapLoaderImplSpecificOptFn`.
+The Loader component uses `LoaderOption` to define loading options. Loader currently has no common Options. Each specific implementation can define its own specific options, wrapped into a unified `LoaderOption` type through the `WrapLoaderImplSpecificOptFn` function.
-## Usage
+## **Usage**
-### Standalone
+### **Standalone Usage**
-> Code: `eino-ext/components/document/loader/file/examples/fileloader`
+> Code location: eino-ext/components/document/loader/file/examples/fileloader
```go
import (
@@ -81,32 +89,43 @@ import (
"github.com/cloudwego/eino-ext/components/document/loader/file"
)
-loader, _ := file.NewFileLoader(ctx, &file.FileLoaderConfig{ UseNameAsID: true })
+// Initialize loader (using file loader as example)
+loader, _ := file.NewFileLoader(ctx, &file.FileLoaderConfig{
+ // Configuration parameters
+ UseNameAsID: true,
+})
+// Load document
filePath := "../../testdata/test.md"
-docs, _ := loader.Load(ctx, document.Source{ URI: filePath })
+docs, _ := loader.Load(ctx, document.Source{
+ URI: filePath,
+})
+
log.Printf("doc content: %v", docs[0].Content)
```
-### In Orchestration
+### **Usage in Orchestration**
```go
-// Chain
+// Use in Chain
chain := compose.NewChain[string, []*schema.Document]()
chain.AppendLoader(loader)
+
+// Compile and run
runnable, _ := chain.Compile()
+
result, _ := runnable.Invoke(ctx, input)
-// Graph
+// Use in Graph
graph := compose.NewGraph[string, []*schema.Document]()
graph.AddLoaderNode("loader_node", loader)
```
-## Options and Callbacks
+## **Option and Callback Usage**
-### Callback Example
+### **Callback Usage Example**
-> Code: `eino-ext/components/document/loader/file/examples/fileloader`
+> Code location: eino-ext/components/document/loader/file/examples/fileloader
```go
import (
@@ -115,60 +134,83 @@ import (
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
callbacksHelper "github.com/cloudwego/eino/utils/callbacks"
+
"github.com/cloudwego/eino-ext/components/document/loader/file"
)
+// Create callback handler
handler := &callbacksHelper.LoaderCallbackHandler{
OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *document.LoaderCallbackInput) context.Context {
log.Printf("start loading docs...: %s\n", input.Source.URI)
return ctx
},
OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *document.LoaderCallbackOutput) context.Context {
- log.Printf("complete loading docs, total: %d\n", len(output.Docs))
+ log.Printf("complete loading docs, total loaded docs: %d\n", len(output.Docs))
return ctx
},
+ // OnError
}
-helper := callbacksHelper.NewHandlerHelper().Loader(handler).Handler()
+// Use callback handler
+helper := callbacksHelper.NewHandlerHelper().
+ Loader(handler).
+ Handler()
chain := compose.NewChain[document.Source, []*schema.Document]()
chain.AppendLoader(loader)
+// Use at runtime
run, _ := chain.Compile(ctx)
-outDocs, _ := run.Invoke(ctx, document.Source{ URI: filePath }, compose.WithCallbacks(helper))
+outDocs, _ := run.Invoke(ctx, document.Source{
+ URI: filePath,
+}, compose.WithCallbacks(helper))
+
log.Printf("doc content: %v", outDocs[0].Content)
```
-## Existing Implementations
+## **Existing Implementations**
+
+1. File Loader: For loading documents from the local file system [Loader - local file](/docs/eino/ecosystem_integration/document/loader_local_file)
+2. Web Loader: For loading documents pointed to by network URLs [Loader - web url](/docs/eino/ecosystem_integration/document/loader_web_url)
+3. S3 Loader: For loading documents stored in S3-compatible storage systems [Loader - amazon s3](/docs/eino/ecosystem_integration/document/loader_amazon_s3)
-1. File Loader — local filesystem: [Loader — local file](/docs/eino/ecosystem_integration/document/loader_local_file)
-2. Web Loader — HTTP/HTTPS: [Loader — web url](/docs/eino/ecosystem_integration/document/loader_web_url)
-3. S3 Loader — S3-compatible storage: [Loader — Amazon S3](/docs/eino/ecosystem_integration/document/loader_amazon_s3)
+## **Implementation Reference**
-## Implementation Notes
+When implementing your own loader component, pay attention to the option mechanism and callback handling.
-### Option Mechanism
+### Option **Mechanism**
+
+Custom Loader needs to implement its own Option parameter mechanism:
```go
+// Define option struct
type MyLoaderOptions struct {
Timeout time.Duration
RetryCount int
}
+// Define option functions
func WithTimeout(timeout time.Duration) document.LoaderOption {
- return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) { o.Timeout = timeout })
+ return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) {
+ o.Timeout = timeout
+ })
}
func WithRetryCount(count int) document.LoaderOption {
- return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) { o.RetryCount = count })
+ return document.WrapLoaderImplSpecificOptFn(func(o *MyLoaderOptions) {
+ o.RetryCount = count
+ })
}
```
-### Callback Structures
+### **Callback Handling**
-> Code: `eino/components/document/callback_extra_loader.go`
+Loader implementations need to trigger callbacks at appropriate times:
+
+> Code location: eino/components/document/callback_extra_loader.go
```go
+// This is the callback input/output defined by the loader component. Implementations need to satisfy the meaning of the parameters.
type LoaderCallbackInput struct {
Source Source
Extra map[string]any
@@ -181,7 +223,7 @@ type LoaderCallbackOutput struct {
}
```
-### Full Implementation Example
+### **Complete Implementation Example**
```go
import (
@@ -208,6 +250,7 @@ type Config struct {
}
func (l *CustomLoader) Load(ctx context.Context, src document.Source, opts ...document.LoaderOption) ([]*schema.Document, error) {
+ // 1. Handle options
options := &customLoaderOptions{
Timeout: l.timeout,
RetryCount: l.retryCount,
@@ -215,17 +258,21 @@ func (l *CustomLoader) Load(ctx context.Context, src document.Source, opts ...do
options = document.GetLoaderImplSpecificOptions(options, opts...)
var err error
+ // 2. Handle errors and call error callback method
defer func() {
if err != nil {
callbacks.OnError(ctx, err)
}
}()
+ // 3. Callback before loading starts
ctx = callbacks.OnStart(ctx, &document.LoaderCallbackInput{
Source: src,
})
+ // 4. Execute loading logic
docs, err := l.doLoad(ctx, src, options)
+
if err != nil {
return nil, err
}
@@ -239,20 +286,23 @@ func (l *CustomLoader) Load(ctx context.Context, src document.Source, opts ...do
}
func (l *CustomLoader) doLoad(ctx context.Context, src document.Source, opts *customLoaderOptions) ([]*schema.Document, error) {
+ // Implement document loading logic
+ // 1. Load document content
+ // 2. Construct Document object, note that important information like document source can be saved in MetaData
return []*schema.Document{{
Content: "Hello World",
}}, nil
}
```
-### Notes
+### **Notes**
-- `MetaData` is critical for storing document source and other metadata
-- Return meaningful errors on load failures for easier debugging
+- MetaData is an important part of the document, used to save various metadata information of the document
+- Return meaningful error information when document loading fails, making error troubleshooting easier
-## Other References
+## Other Reference Documents
-- [Eino: Document Transformer Guide](/docs/eino/core_modules/components/document_transformer_guide)
-- [Eino: Embedding Guide](/docs/eino/core_modules/components/embedding_guide)
-- [Eino: Indexer Guide](/docs/eino/core_modules/components/indexer_guide)
-- [Eino: Retriever Guide](/docs/eino/core_modules/components/retriever_guide)
+- [[🚧]Eino: Document Transformer User Guide](/docs/eino/core_modules/components/document_transformer_guide)
+- [[🚧]Eino: Embedding User Guide](/docs/eino/core_modules/components/embedding_guide)
+- [[🚧]Eino: Indexer User Guide](/docs/eino/core_modules/components/indexer_guide)
+- [[🚧]Eino: Retriever User Guide](/docs/eino/core_modules/components/retriever_guide)
diff --git a/content/en/docs/eino/core_modules/components/document_transformer_guide.md b/content/en/docs/eino/core_modules/components/document_transformer_guide.md
index fd9a61d7a68..080ba8e7c81 100644
--- a/content/en/docs/eino/core_modules/components/document_transformer_guide.md
+++ b/content/en/docs/eino/core_modules/components/document_transformer_guide.md
@@ -3,24 +3,24 @@ Description: ""
date: "2025-07-21"
lastmod: ""
tags: []
-title: 'Eino: Document Transformer Guide'
-weight: 9
+title: 'Eino: Document Transformer User Guide'
+weight: 3
---
-## **Overview**
+## **Introduction**
-Document Transformer is a component for transforming and processing documents. It performs operations such as splitting, filtering, merging, and more to produce documents tailored to specific needs. Typical scenarios include:
+Document Transformer is a component for document conversion and processing. Its main purpose is to perform various transformation operations on input documents, such as splitting, filtering, merging, etc., to obtain documents that meet specific requirements. This component can be used in the following scenarios:
-- Split long documents into smaller chunks for processing
-- Filter document content by rules
-- Convert document structure
-- Extract specific parts of a document
+- Splitting long documents into small paragraphs for easier processing
+- Filtering document content based on specific rules
+- Performing structural transformations on document content
+- Extracting specific parts of documents
## **Component Definition**
-### **Interface**
+### **Interface Definition**
-> Code: `eino/components/document/interface.go`
+> Code location: eino/components/document/interface.go
```go
type Transformer interface {
@@ -30,48 +30,48 @@ type Transformer interface {
#### **Transform Method**
-- Purpose: transform input documents
-- Params:
- - `ctx`: request context, also carries the Callback Manager
- - `src`: documents to process
- - `opts`: options to configure behavior
-- Returns:
- - `[]*schema.Document`: transformed documents
- - `error`: error during transformation
+- Function: Perform transformation processing on input documents
+- Parameters:
+ - ctx: Context object for passing request-level information and Callback Manager
+ - src: List of documents to be processed
+ - opts: Optional parameters for configuring transformation behavior
+- Return values:
+ - `[]*schema.Document`: Transformed document list
+ - error: Error information during transformation
### **Document Struct**
```go
type Document struct {
- // ID is the unique identifier
- ID string
- // Content is the document text
+ // ID is the unique identifier of the document
+ ID string
+ // Content is the content of the document
Content string
- // MetaData stores metadata
+ // MetaData stores metadata information of the document
MetaData map[string]any
}
```
-Key fields:
+The Document struct is the standard format for documents, containing the following important fields:
-- ID: unique identifier
-- Content: actual text
-- MetaData: metadata such as:
- - source info
- - vector representation (for retrieval)
- - score (for ranking)
- - sub-index (for hierarchical retrieval)
- - other custom metadata
+- ID: Unique identifier of the document, used to uniquely identify a document in the system
+- Content: Actual content of the document
+- MetaData: Metadata of the document, can store information such as:
+ - Source information of the document
+ - Vector representation of the document (for vector retrieval)
+ - Document score (for ranking)
+ - Document sub-index (for hierarchical retrieval)
+ - Other custom metadata
### **Common Options**
-Transformer uses `TransformerOption` for optional parameters. There are currently no global/common options; each implementation defines its own specific options, wrapped via `WrapTransformerImplSpecificOptFn` into `TransformerOption`.
+The Transformer component uses TransformerOption to define optional parameters. There are currently no common options. Each specific implementation can define its own specific Options, wrapped into a unified TransformerOption type through the WrapTransformerImplSpecificOptFn function.
## **Usage**
-### **Standalone**
+### **Standalone Usage**
-> Code: `eino-ext/components/document/transformer/splitter/markdown/examples/headersplitter`
+> Code location: eino-ext/components/document/transformer/splitter/markdown/examples/headersplitter
```go
import (
@@ -79,8 +79,9 @@ import (
"github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown"
)
-// init transformer (markdown example)
+// Initialize transformer (using markdown as example)
transformer, _ := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{
+ // Configuration parameters
Headers: map[string]string{
"##": "",
},
@@ -89,7 +90,7 @@ transformer, _ := markdown.NewHeaderSplitter(ctx, &markdown.HeaderConfig{
markdownDoc := &schema.Document{
Content: "## Title 1\nHello Word\n## Title 2\nWord Hello",
}
-// transform
+// Transform document
transformedDocs, _ := transformer.Transform(ctx, []*schema.Document{markdownDoc})
for idx, doc := range transformedDocs {
@@ -97,23 +98,23 @@ for idx, doc := range transformedDocs {
}
```
-### **In Orchestration**
+### **Usage in Orchestration**
```go
-// in Chain
+// Use in Chain
chain := compose.NewChain[[]*schema.Document, []*schema.Document]()
chain.AppendDocumentTransformer(transformer)
-// in Graph
+// Use in Graph
graph := compose.NewGraph[[]*schema.Document, []*schema.Document]()
graph.AddDocumentTransformerNode("transformer_node", transformer)
```
-## **Options and Callbacks**
+## **Option and Callback Usage**
-### **Callback Example**
+### **Callback Usage Example**
-> Code: `eino-ext/components/document/transformer/splitter/markdown/examples/headersplitter`
+> Code location: eino-ext/components/document/transformer/splitter/markdown/examples/headersplitter
```go
import (
@@ -126,6 +127,7 @@ import (
"github.com/cloudwego/eino-ext/components/document/transformer/splitter/markdown"
)
+// Create callback handler
handler := &callbacksHelper.TransformerCallbackHandler{
OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *document.TransformerCallbackInput) context.Context {
log.Printf("input access, len: %v, content: %s\n", len(input.Input), input.Input[0].Content)
@@ -135,8 +137,10 @@ handler := &callbacksHelper.TransformerCallbackHandler{
log.Printf("output finished, len: %v\n", len(output.Output))
return ctx
},
+ // OnError
}
+// Use callback handler
helper := callbacksHelper.NewHandlerHelper().
Transformer(handler).
Handler()
@@ -144,6 +148,7 @@ helper := callbacksHelper.NewHandlerHelper().
chain := compose.NewChain[[]*schema.Document, []*schema.Document]()
chain.AppendDocumentTransformer(transformer)
+// Use at runtime
run, _ := chain.Compile(ctx)
outDocs, _ := run.Invoke(ctx, []*schema.Document{markdownDoc}, compose.WithCallbacks(helper))
@@ -155,26 +160,30 @@ for idx, doc := range outDocs {
## **Existing Implementations**
-1. Markdown Header Splitter: split by markdown headers — [Splitter - markdown](/docs/eino/ecosystem_integration/document/splitter_markdown)
-2. Text Splitter: split by length or separators — [Splitter - semantic](/docs/eino/ecosystem_integration/document/splitter_semantic)
-3. Document Filter: filter by rules — [Splitter - recursive](/docs/eino/ecosystem_integration/document/splitter_recursive)
+1. Markdown Header Splitter: Document splitting based on Markdown headers [Splitter - markdown](/docs/eino/ecosystem_integration/document/splitter_markdown)
+2. Text Splitter: Document splitting based on text length or delimiters [Splitter - semantic](/docs/eino/ecosystem_integration/document/splitter_semantic)
+3. Document Filter: Filter document content based on rules [Splitter - recursive](/docs/eino/ecosystem_integration/document/splitter_recursive)
-## **Implement Your Own**
+## **Implementation Reference**
-Consider the following when implementing a custom Transformer:
+When implementing a custom Transformer component, note the following:
1. Option handling
2. Callback handling
### **Option Mechanism**
+Custom Transformer needs to implement its own Option mechanism:
+
```go
+// Define Option struct
type MyTransformerOptions struct {
ChunkSize int
Overlap int
MinChunkLength int
}
+// Define Option functions
func WithChunkSize(size int) document.TransformerOption {
return document.WrapTransformerImplSpecificOptFn(func(o *MyTransformerOptions) {
o.ChunkSize = size
@@ -190,7 +199,10 @@ func WithOverlap(overlap int) document.TransformerOption {
### **Callback Handling**
+Transformer implementations need to trigger callbacks at appropriate times:
+
```go
+// This is the callback input/output defined by transformer. Custom components need to satisfy the meaning of the structures when implementing.
type TransformerCallbackInput struct {
Input []*schema.Document
Extra map[string]any
@@ -202,7 +214,7 @@ type TransformerCallbackOutput struct {
}
```
-### **Full Implementation Example**
+### **Complete Implementation Example**
```go
type MyTransformer struct {
@@ -220,7 +232,7 @@ func NewMyTransformer(config *MyTransformerConfig) (*MyTransformer, error) {
}
func (t *MyTransformer) Transform(ctx context.Context, src []*schema.Document, opts ...document.TransformerOption) ([]*schema.Document, error) {
- // 1. handle Option
+ // 1. Handle Options
options := &MyTransformerOptions{
ChunkSize: t.chunkSize,
Overlap: t.overlap,
@@ -228,15 +240,15 @@ func (t *MyTransformer) Transform(ctx context.Context, src []*schema.Document, o
}
options = document.GetTransformerImplSpecificOptions(options, opts...)
- // 2. before-transform callback
+ // 2. Callback before transformation starts
ctx = callbacks.OnStart(ctx, info, &document.TransformerCallbackInput{
Input: src,
})
- // 3. perform transform
+ // 3. Execute transformation logic
docs, err := t.doTransform(ctx, src, options)
- // 4. handle error and finish callback
+ // 4. Handle errors and completion callback
if err != nil {
ctx = callbacks.OnError(ctx, info, err)
return nil, err
@@ -248,22 +260,20 @@ func (t *MyTransformer) Transform(ctx context.Context, src []*schema.Document, o
return docs, nil
}
-```
-```go
func (t *MyTransformer) doTransform(ctx context.Context, src []*schema.Document, opts *MyTransformerOptions) ([]*schema.Document, error) {
- // implement document transform logic
+ // Implement document transformation logic
return docs, nil
}
```
### **Notes**
-- Preserve and manage metadata when transforming documents; keep original metadata and add custom metadata as needed.
+- Pay attention to metadata handling for transformed documents, preserving original metadata and adding custom metadata
-## **References**
+## Other Reference Documents
-- [Eino: Embedding Guide](/docs/eino/core_modules/components/embedding_guide)
-- [Eino: Indexer Guide](/docs/eino/core_modules/components/indexer_guide)
-- [Eino: Retriever Guide](/docs/eino/core_modules/components/retriever_guide)
-- [Eino: Document Loader Guide](/docs/eino/core_modules/components/document_loader_guide)
+- [[🚧]Eino: Embedding User Guide](/docs/eino/core_modules/components/embedding_guide)
+- [[🚧]Eino: Indexer User Guide](/docs/eino/core_modules/components/indexer_guide)
+- [[🚧]Eino: Retriever User Guide](/docs/eino/core_modules/components/retriever_guide)
+- [[🚧]Eino: Document Loader User Guide](/docs/eino/core_modules/components/document_loader_guide)
diff --git a/content/en/docs/eino/core_modules/components/embedding_guide.md b/content/en/docs/eino/core_modules/components/embedding_guide.md
index 1a921998341..b0cf613360d 100644
--- a/content/en/docs/eino/core_modules/components/embedding_guide.md
+++ b/content/en/docs/eino/core_modules/components/embedding_guide.md
@@ -3,21 +3,21 @@ Description: ""
date: "2025-07-21"
lastmod: ""
tags: []
-title: 'Eino: Embedding Guide'
-weight: 7
+title: 'Eino: Embedding User Guide'
+weight: 2
---
-## Introduction
+## **Introduction**
-The `Embedding` component converts text into vector representations, mapping content into a vector space where semantically similar texts are closer. It’s useful for:
+The Embedding component is a component for converting text into vector representations. Its main purpose is to map text content into vector space, so that semantically similar texts are closer in vector space. This component plays an important role in the following scenarios:
-- Text similarity
+- Text similarity calculation
- Semantic search
-- Clustering analysis
+- Text clustering analysis
-## Component Definition
+## **Component Definition**
-### Interface
+### **Interface Definition**
```go
type Embedder interface {
@@ -25,38 +25,40 @@ type Embedder interface {
}
```
-#### EmbedStrings
+#### **EmbedStrings Method**
-- Purpose: convert a list of texts into vectors
-- Params:
- - `ctx`: request context and callback manager
- - `texts`: list of texts
- - `opts`: embedding options
-- Returns:
- - `[][]float64`: vectors (dimensions depend on implementation)
- - `error`
+- Function: Convert a set of texts into vector representations
+- Parameters:
+ - ctx: Context object for passing request-level information and Callback Manager
+ - texts: List of texts to be converted
+ - opts: Conversion options for configuring conversion behavior
+- Return values:
+ - `[][]float64`: List of vector representations for the texts, the dimension of each vector is determined by the specific implementation
+ - error: Error information during conversion
-### Common Options
+### **Common Options**
-Embedding uses `EmbeddingOption`. Implementations may define specific options and wrap via `WrapEmbeddingImplSpecificOptFn`.
+The Embedding component uses EmbeddingOption to define optional parameters. Below are the abstracted common options. Each specific implementation can define its own specific Options, wrapped into a unified EmbeddingOption type through the WrapEmbeddingImplSpecificOptFn function.
```go
type Options struct {
+ // Model is the model name used for generating vectors
Model *string
}
```
-Set options:
+Options can be set in the following ways:
```go
+// Set model name
WithModel(model string) Option
```
-## Usage
+## **Usage**
-### Standalone
+### **Standalone Usage**
-> Code: `eino-ext/components/embedding/openai/examples/embedding`
+> Code location: eino-ext/components/embedding/openai/examples/embedding
```go
import "github.com/cloudwego/eino-ext/components/embedding/openai"
@@ -71,33 +73,34 @@ embedder, _ := openai.NewEmbedder(ctx, &openai.EmbeddingConfig{
vectorIDs, _ := embedder.EmbedStrings(ctx, []string{"hello", "how are you"})
```
-### In Orchestration
+### **Usage in Orchestration**
-> Code: `eino-ext/components/embedding/openai/examples/embedding`
+> Code location: eino-ext/components/embedding/openai/examples/embedding
```go
-// Chain
+// Use in Chain
chain := compose.NewChain[[]string, [][]float64]()
chain.AppendEmbedding(embedder)
-// Graph
+// Use in Graph
graph := compose.NewGraph[[]string, [][]float64]()
graph.AddEmbeddingNode("embedding_node", embedder)
```
-## Options and Callbacks
+## **Option and Callback Usage**
-### Options Example
+### **Option Usage Example**
```go
+// Using options (standalone component usage example)
vectors, err := embedder.EmbedStrings(ctx, texts,
embedding.WithModel("text-embedding-3-small"),
)
```
-### Callback Example
+### **Callback Usage Example**
-> Code: `eino-ext/components/embedding/openai/examples/embedding`
+> Code location: eino-ext/components/embedding/openai/examples/embedding
```go
import (
@@ -123,59 +126,86 @@ callbackHandler := callbacksHelper.NewHandlerHelper().Embedding(handler).Handler
chain := compose.NewChain[[]string, [][]float64]()
chain.AppendEmbedding(embedder)
+
+// Compile and run
runnable, _ := chain.Compile(ctx)
-vectors, _ = runnable.Invoke(ctx, []string{"hello", "how are you"}, compose.WithCallbacks(callbackHandler))
+vectors, _ = runnable.Invoke(ctx, []string{"hello", "how are you"},
+ compose.WithCallbacks(callbackHandler))
+
log.Printf("vectors in chain: %v", vectors)
```
-## Existing Implementations
+## **Existing Implementations**
+
+1. OpenAI Embedding: Generate vectors using OpenAI's text embedding models [Embedding - OpenAI](/docs/eino/ecosystem_integration/embedding/embedding_openai)
+2. ARK Embedding: Generate vectors using ARK platform models [Embedding - ARK](/docs/eino/ecosystem_integration/embedding/embedding_ark)
-1. OpenAI Embedding: [Embedding — OpenAI](/docs/eino/ecosystem_integration/embedding/embedding_openai)
-2. ARK Embedding: [Embedding — ARK](/docs/eino/ecosystem_integration/embedding/embedding_ark)
+## **Implementation Reference**
-## Implementation Notes
+When implementing a custom Embedding component, note the following:
-1. Handle common options
-2. Implement callback mechanisms
+1. Make sure to handle common options
+2. Make sure to implement callback mechanism
-### Options
+### **Option Mechanism**
+
+Custom Embedding needs to implement its own Option mechanism:
```go
+// Define Option struct
type MyEmbeddingOptions struct {
BatchSize int
MaxRetries int
Timeout time.Duration
}
+// Define Option functions
func WithBatchSize(size int) embedding.Option {
- return embedding.WrapEmbeddingImplSpecificOptFn(func(o *MyEmbeddingOptions) { o.BatchSize = size })
+ return embedding.WrapEmbeddingImplSpecificOptFn(func(o *MyEmbeddingOptions) {
+ o.BatchSize = size
+ })
}
```
-### Callback Structures
+### **Callback Handling**
+
+Embedder implementations need to trigger callbacks at appropriate times. The framework has defined standard callback input/output structs:
```go
+// CallbackInput is the input for embedding callback
type CallbackInput struct {
+ // Texts is the list of texts to be converted
Texts []string
+ // Config is the configuration for generating vectors
Config *Config
+ // Extra is additional information for the callback
Extra map[string]any
}
+// CallbackOutput is the output for embedding callback
type CallbackOutput struct {
+ // Embeddings is the list of generated vectors
Embeddings [][]float64
+ // Config is the configuration for generating vectors
Config *Config
+ // TokenUsage is the token usage information
TokenUsage *TokenUsage
+ // Extra is additional information for the callback
Extra map[string]any
}
+// TokenUsage is the token usage information
type TokenUsage struct {
+ // PromptTokens is the number of tokens in the prompt
PromptTokens int
+ // CompletionTokens is the number of tokens in the completion
CompletionTokens int
+ // TotalTokens is the total number of tokens
TotalTokens int
}
```
-### Full Implementation Example
+### **Complete Implementation Example**
```go
type MyEmbedder struct {
@@ -191,7 +221,7 @@ func NewMyEmbedder(config *MyEmbedderConfig) (*MyEmbedder, error) {
}
func (e *MyEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...embedding.Option) ([][]float64, error) {
- // 1. handle options
+ // 1. Handle options
options := &MyEmbeddingOptions{
Options: &embedding.Options{},
BatchSize: e.batchSize,
@@ -199,10 +229,10 @@ func (e *MyEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...e
options.Options = embedding.GetCommonOptions(options.Options, opts...)
options = embedding.GetImplSpecificOptions(options.Options, opts...)
- // 2. get callback manager
+ // 2. Get callback manager
cm := callbacks.ManagerFromContext(ctx)
- // 3. before-embed callback
+ // 3. Callback before generation starts
ctx = cm.OnStart(ctx, info, &embedding.CallbackInput{
Texts: texts,
Config: &embedding.Config{
@@ -210,10 +240,10 @@ func (e *MyEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...e
},
})
- // 4. perform embedding
+ // 4. Execute vector generation logic
vectors, tokenUsage, err := e.doEmbed(ctx, texts, options)
- // 5. handle error and finish callback
+ // 5. Handle errors and completion callback
if err != nil {
ctx = cm.OnError(ctx, info, err)
return nil, err
@@ -231,13 +261,13 @@ func (e *MyEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...e
}
func (e *MyEmbedder) doEmbed(ctx context.Context, texts []string, opts *MyEmbeddingOptions) ([][]float64, *TokenUsage, error) {
- // implement logic
+ // Implement logic
return vectors, tokenUsage, nil
}
```
-## References
+## Other Reference Documents
-- [Eino: Document Loader Guide](/docs/eino/core_modules/components/document_loader_guide)
-- [Eino: Indexer Guide](/docs/eino/core_modules/components/indexer_guide)
-- [Eino: Retriever Guide](/docs/eino/core_modules/components/retriever_guide)
+- [Eino: Document Loader User Guide](/docs/eino/core_modules/components/document_loader_guide)
+- [Eino: Indexer User Guide](/docs/eino/core_modules/components/indexer_guide)
+- [Eino: Retriever User Guide](/docs/eino/core_modules/components/retriever_guide)
diff --git a/content/en/docs/eino/core_modules/components/indexer_guide.md b/content/en/docs/eino/core_modules/components/indexer_guide.md
index 03d7432933d..08faa057797 100644
--- a/content/en/docs/eino/core_modules/components/indexer_guide.md
+++ b/content/en/docs/eino/core_modules/components/indexer_guide.md
@@ -3,15 +3,15 @@ Description: ""
date: "2026-01-20"
lastmod: ""
tags: []
-title: 'Eino: Indexer Guide'
-weight: 6
+title: 'Eino: Indexer User Guide'
+weight: 5
---
## **Introduction**
-The Indexer component is used to store and index documents. Its main purpose is to store documents and their vector representations into backend storage systems and provide efficient retrieval capabilities. This component plays an important role in the following scenarios:
+The Indexer component is a component for storing and indexing documents. Its main purpose is to store documents and their vector representations in backend storage systems and provide efficient retrieval capabilities. This component plays an important role in the following scenarios:
-- Building vector databases for semantic search
+- Building vector databases for semantic association search
## **Component Definition**
@@ -27,12 +27,12 @@ type Indexer interface {
#### **Store Method**
-- Purpose: Store documents and build indexes
+- Function: Store documents and build indexes
- Parameters:
- - ctx: Context object for passing request-level information and the Callback Manager
- - docs: List of documents to store
+ - ctx: Context object for passing request-level information and Callback Manager
+ - docs: List of documents to be stored
- opts: Storage options for configuring storage behavior
-- Returns:
+- Return values:
- ids: List of successfully stored document IDs
- error: Error information during storage
@@ -42,14 +42,14 @@ The Indexer component uses IndexerOption to define optional parameters. Indexer
```go
type Options struct {
- // SubIndexes is the list of sub-indexes to build
+ // SubIndexes is the list of sub-indexes to be built
SubIndexes []string
// Embedding is the component used to generate document vectors
Embedding embedding.Embedder
}
```
-Options can be set as follows:
+Options can be set in the following ways:
```go
// Set sub-indexes
@@ -73,18 +73,18 @@ import (
collectionName := "eino_test"
/*
- * In the following example, a dataset (collection) named eino_test is pre-created with fields:
+ * The example below pre-built a dataset (collection) named eino_test with the following field configuration:
* Field Name Field Type Vector Dimension
- * ID string
- * vector vector 1024
+ * ID string
+ * vector vector 1024
* sparse_vector sparse_vector
- * content string
+ * content string
* extra_field_1 string
*
- * Component usage notes:
- * 1. Field names and types for ID / vector / sparse_vector / content must match the above configuration
- * 2. The vector dimension must match the output dimension of the model indicated by ModelName
- * 3. Some models do not output sparse vectors; set UseSparse=false and the collection may omit sparse_vector
+ * Notes when using the component:
+ * 1. Field names and types for ID / vector / sparse_vector / content should match the configuration above
+ * 2. Vector dimension should match the output dimension of the model corresponding to ModelName
+ * 3. Some models don't output sparse vectors, in which case UseSparse should be set to false, and the collection may not have a sparse_vector field
*/
cfg := &volc_vikingdb.IndexerConfig{
@@ -244,14 +244,14 @@ docs := []*schema.Document{
ids, err := indexer.Store(ctx, docs)
```
-### **Using in Orchestration**
+### **Usage in Orchestration**
```go
-// Using in Chain
+// Use in Chain
chain := compose.NewChain[[]*schema.Document, []string]()
chain.AppendIndexer(indexer)
-// Using in Graph
+// Use in Graph
graph := compose.NewGraph[[]*schema.Document, []string]()
graph.AddIndexerNode("indexer_node", indexer)
```
@@ -302,7 +302,7 @@ handler := &callbacksHelper.IndexerCallbackHandler{
// OnError
}
-// Using callback handler
+// Use callback handler
helper := callbacksHelper.NewHandlerHelper().
Indexer(handler).
Handler()
@@ -310,7 +310,7 @@ helper := callbacksHelper.NewHandlerHelper().
chain := compose.NewChain[[]*schema.Document, []string]()
chain.AppendIndexer(volcIndexer)
-// Using at runtime
+// Use at runtime
run, _ := chain.Compile(ctx)
outIDs, _ := run.Invoke(ctx, docs, compose.WithCallbacks(helper))
@@ -320,20 +320,20 @@ fmt.Printf("vikingDB store success, docs=%v, resp ids=%v\n", docs, outIDs)
## **Existing Implementations**
-- Volc VikingDB Indexer: Vector database indexer based on Volcano Engine VikingDB [Indexer - VikingDB](/docs/eino/ecosystem_integration/indexer/indexer_volc_vikingdb)
+- Volc VikingDB Indexer: Vector database indexer based on Volcengine VikingDB [Indexer - VikingDB](/docs/eino/ecosystem_integration/indexer/indexer_volc_vikingdb)
- Milvus v2.5+ Indexer: Vector database indexer based on Milvus [Indexer - Milvus 2 (v2.5+)](/docs/eino/ecosystem_integration/indexer/indexer_milvusv2)
- Milvus v2.4- Indexer: Vector database indexer based on Milvus [Indexer - Milvus (v2.4-)](/docs/eino/ecosystem_integration/indexer/indexer_milvus)
- Elasticsearch 8 Indexer: General search engine indexer based on ES8 [Indexer - ElasticSearch 8](/docs/eino/ecosystem_integration/indexer/indexer_es8)
-- ElasticSearch 7 Indexer: General search engine indexer based on ES7 [Indexer - Elasticsearch 7](/docs/eino/ecosystem_integration/indexer/indexer_elasticsearch7)
+- ElasticSearch 7 Indexer: General search engine indexer based on ES7 [Indexer - Elasticsearch 7 ](/docs/eino/ecosystem_integration/indexer/indexer_elasticsearch7)
- OpenSearch 3 Indexer: General search engine indexer based on OpenSearch 3 [Indexer - OpenSearch 3](/docs/eino/ecosystem_integration/indexer/indexer_opensearch3)
- OpenSearch 2 Indexer: General search engine indexer based on OpenSearch 2 [Indexer - OpenSearch 2](/docs/eino/ecosystem_integration/indexer/indexer_opensearch2)
-## **Custom Implementation Reference**
+## **Implementation Reference**
When implementing a custom Indexer component, note the following:
-1. Handle common options and implementation-specific options properly
-2. Handle callbacks properly
+1. Pay attention to handling common options as well as implementation-level option handling
+2. Pay attention to callback handling
### **Option Mechanism**
@@ -346,7 +346,7 @@ type MyIndexerOptions struct {
MaxRetries int
}
-// Define Option function
+// Define Option functions
func WithBatchSize(size int) indexer.Option {
return indexer.WrapIndexerImplSpecificOptFn(func(o *MyIndexerOptions) {
o.BatchSize = size
@@ -356,7 +356,7 @@ func WithBatchSize(size int) indexer.Option {
### **Callback Handling**
-Indexer implementations need to trigger callbacks at appropriate times. The framework has defined standard callback input/output structures:
+Indexer implementations need to trigger callbacks at appropriate times. The framework has defined standard callback input/output structs:
```go
// CallbackInput is the input for indexer callback
@@ -399,7 +399,7 @@ func (i *MyIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...
// 2. Get callback manager
cm := callbacks.ManagerFromContext(ctx)
- // 3. Callback before storage
+ // 3. Callback before storage starts
ctx = cm.OnStart(ctx, info, &indexer.CallbackInput{
Docs: docs,
})
@@ -407,7 +407,7 @@ func (i *MyIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...
// 4. Execute storage logic
ids, err := i.doStore(ctx, docs, options)
- // 5. Handle error and completion callback
+ // 5. Handle errors and completion callback
if err != nil {
ctx = cm.OnError(ctx, info, err)
return nil, err
@@ -421,7 +421,7 @@ func (i *MyIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...
}
func (i *MyIndexer) doStore(ctx context.Context, docs []*schema.Document, opts *indexer.Options) ([]string, error) {
- // Implement document storage logic (handle common option parameters)
+ // Implement document storage logic (pay attention to handling common option parameters)
// 1. If Embedding component is set, generate vector representations for documents
if opts.Embedding != nil {
// Extract document content
@@ -434,7 +434,7 @@ func (i *MyIndexer) doStore(ctx context.Context, docs []*schema.Document, opts *
if err != nil {
return nil, err
}
- // Store vectors in document MetaData
+ // Store vectors in document's MetaData
for j, doc := range docs {
doc.WithVector(vectors[j])
}
diff --git a/content/en/docs/eino/core_modules/components/lambda_guide.md b/content/en/docs/eino/core_modules/components/lambda_guide.md
index da5dd9fb077..f6786b854d4 100644
--- a/content/en/docs/eino/core_modules/components/lambda_guide.md
+++ b/content/en/docs/eino/core_modules/components/lambda_guide.md
@@ -3,122 +3,150 @@ Description: ""
date: "2025-11-20"
lastmod: ""
tags: []
-title: 'Eino: Lambda Guide'
-weight: 5
+title: 'Eino: Lambda User Guide'
+weight: 4
---
-## Introduction
+## **Introduction**
-`Lambda` is the simplest component type, allowing you to embed custom function logic in a workflow. Lambdas can implement one or more of the four paradigms formed by streaming/non-streaming input/output: `Invoke`, `Stream`, `Collect`, `Transform`.
+Lambda is the most basic component type in Eino. It allows users to embed custom function logic in workflows. The Lambda component is composed of 4 types of execution functions based on whether input and output are streams, corresponding to 4 interaction modes: Invoke, Stream, Collect, Transform.
-The framework converts among paradigms under defined rules. See [Overview](/docs/eino/overview) for details (Runnable section).
+Users can implement one or more of these when building Lambda, and the framework will convert them according to certain rules. For detailed introduction, see: [Eino: Overview](/docs/eino/overview) (see the Runnable section)
-## Definition and Construction
+## **Component Definition and Implementation**
-> Code: `eino/compose/types_lambda.go`
+The core of the Lambda component is the `Lambda` struct, which wraps user-provided Lambda functions. Users can create a Lambda component through construction methods:
+
+> Code location: eino/compose/types_lambda.go
```go
-type Lambda struct { executor *composableRunnable }
+type Lambda struct {
+ executor *composableRunnable
+}
```
-Lambda function signatures:
+The four function types supported by Lambda are defined as follows, meaning user-provided Lambda functions need to satisfy these function signatures:
```go
type Invoke[I, O, TOption any] func(ctx context.Context, input I, opts ...TOption) (output O, err error)
+
type Stream[I, O, TOption any] func(ctx context.Context, input I, opts ...TOption) (output *schema.StreamReader[O], err error)
+
type Collect[I, O, TOption any] func(ctx context.Context, input *schema.StreamReader[I], opts ...TOption) (output O, err error)
+
type Transform[I, O, TOption any] func(ctx context.Context, input *schema.StreamReader[I], opts ...TOption) (output *schema.StreamReader[O], err error)
```
## Usage
-> Examples: [https://github.com/cloudwego/eino-examples/blob/main/components/lambda](https://github.com/cloudwego/eino-examples/blob/main/components/lambda)
+> Code in examples references: [https://github.com/cloudwego/eino-examples/blob/main/components/lambda](https://github.com/cloudwego/eino-examples/blob/main/components/lambda)
-### Constructors
+### Construction Methods
-Eino components generally accept `func(ctx, input, ...option) (output, error)`. For lambdas, simpler constructors are provided:
+From the unified specification of Eino component interfaces, a component's callable method needs to have 3 input parameters and 2 output parameters: func (ctx, input, ...option) (output, error). However, in scenarios using Lambda, developers often want to add a Lambda node by providing a simple function implementation, so construction methods are divided into 3 types:
-- Provide exactly one paradigm function
- - Without custom options
- - With custom options
-- Provide any subset of the four paradigms via `AnyLambda`
+- Provide only one selected interaction function with determined input/output stream types
+ - Without custom Option
+ - Using custom Option
+- Customize n (n<=4) of the 4 interaction functions: AnyLambda
-#### Without Custom Options
+#### Without Custom Option
-- `InvokableLambda`
+- InvokableLambda
```go
-// input and output can be any custom types
-lambda := compose.InvokableLambda(func(ctx context.Context, input string) (string, error) {
+// input and output types can be any custom types
+lambda := compose.InvokableLambda(func(ctx context.Context, input string) (output string, err error) {
// some logic
})
```
-- `StreamableLambda`
+- StreamableLambda
```go
-// input is any type; output must be *schema.StreamReader[O]
-lambda := compose.StreamableLambda(func(ctx context.Context, input string) (*schema.StreamReader[string], error) {
+// input can be any type, output must be *schema.StreamReader[O], where O can be any type
+lambda := compose.StreamableLambda(func(ctx context.Context, input string) (output *schema.StreamReader[string], err error) {
// some logic
})
```
-- `CollectableLambda`
+- CollectableLambda
```go
-// input must be *schema.StreamReader[I]; output can be any type
-lambda := compose.CollectableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (string, error) {
+// input must be *schema.StreamReader[I], where I can be any type, output can be any type
+lambda := compose.CollectableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (output string, err error) {
// some logic
})
```
-- `TransformableLambda`
+- TransformableLambda
```go
-// input and output must be *schema.StreamReader[I]
-lambda := compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (*schema.StreamReader[string], error) {
+// input and output must be *schema.StreamReader[I], where I can be any type
+lambda := compose.TransformableLambda(func(ctx context.Context, input *schema.StreamReader[string]) (output *schema.StreamReader[string], err error) {
// some logic
})
```
-Shared options:
+- The construction methods for the four Lambda methods have the following common Option options:
+- compose.WithLambdaType(): Modify the Component type of the Lambda component, default is: Lambda
+- compose.WithLambdaCallbackEnable(): Disable the Node Callback that is enabled by default for Lambda component in Graph
-- `compose.WithLambdaType()` — change component type (default: Lambda)
-- `compose.WithLambdaCallbackEnable()` — disable default node callbacks in Graph
+#### Using Custom Option
-#### With Custom Options
+Each interaction mode has a corresponding construction method. Here's an example using Invoke:
```go
-type Options struct { Field1 string }
+type Options struct {
+ Field1 string
+}
type MyOption func(*Options)
lambda := compose.InvokableLambdaWithOption(
- func(ctx context.Context, input string, opts ...MyOption) (string, error) {
- // handle opts
+ func(ctx context.Context, input string, opts ...MyOption) (output string, err error) {
+ // Handle opts
// some logic
- },
+ }
)
```
#### AnyLambda
-Implement multiple paradigms at once:
+AnyLambda allows implementing Lambda function types with multiple interaction modes simultaneously:
```go
-type Options struct { Field1 string }
+type Options struct {
+ Field1 string
+}
+
type MyOption func(*Options)
+// input and output types can be any custom types
lambda, err := compose.AnyLambda(
- func(ctx context.Context, input string, opts ...MyOption) (string, error) { /* ... */ },
- func(ctx context.Context, input string, opts ...MyOption) (*schema.StreamReader[string], error) { /* ... */ },
- func(ctx context.Context, input *schema.StreamReader[string], opts ...MyOption) (string, error) { /* ... */ },
- func(ctx context.Context, input *schema.StreamReader[string], opts ...MyOption) (*schema.StreamReader[string], error) { /* ... */ },
+ // Invoke function
+ func(ctx context.Context, input string, opts ...MyOption) (output string, err error) {
+ // some logic
+ },
+ // Stream function
+ func(ctx context.Context, input string, opts ...MyOption) (output *schema.StreamReader[string], err error) {
+ // some logic
+ },
+ // Collect function
+ func(ctx context.Context, input *schema.StreamReader[string], opts ...MyOption) (output string, err error) {
+ // some logic
+ },
+ // Transform function
+ func(ctx context.Context, input *schema.StreamReader[string], opts ...MyOption) (output *schema.StreamReader[string], err error) {
+ // some logic
+ },
)
```
-### In Orchestration
+### **Usage in Orchestration**
+
+#### Usage in Graph
-#### Graph
+Lambda nodes can be added in Graph through AddLambdaNode:
```go
graph := compose.NewGraph[string, *MyStruct]()
@@ -130,7 +158,9 @@ graph.AddLambdaNode(
)
```
-#### Chain
+#### Usage in Chain
+
+Lambda nodes can be added in Chain through AppendLambda:
```go
chain := compose.NewChain[string, string]()
@@ -139,43 +169,46 @@ chain.AppendLambda(compose.InvokableLambda(func(ctx context.Context, input strin
}))
```
-### Built-in Lambdas
+### Two Built-in Lambdas
#### ToList
-Convert a single element into a one-item slice:
+ToList is a built-in Lambda for converting a single input element to a slice containing that element:
```go
+// Create a ToList Lambda
lambda := compose.ToList[*schema.Message]()
+
+// Use in Chain
chain := compose.NewChain[[]*schema.Message, []*schema.Message]()
-chain.AppendChatModel(chatModel)
-chain.AppendLambda(lambda)
+chain.AppendChatModel(chatModel) // chatModel returns *schema.Message
+chain.AppendLambda(lambda) // Convert *schema.Message to []*schema.Message
```
#### MessageParser
-Parse a JSON message (often from an LLM) into a struct:
+MessageParser is a built-in Lambda for parsing JSON messages (usually generated by LLM) into specified structs:
```go
-// define target struct
+// Define target struct for parsing
type MyStruct struct {
ID int `json:"id"`
}
-// create parser
+// Create parser
parser := schema.NewMessageJSONParser[*MyStruct](&schema.MessageJSONParseConfig{
ParseFrom: schema.MessageParseFromContent,
- ParseKeyPath: "", // use "key.sub.grandsub" to parse subfields
+ ParseKeyPath: "", // If you only need to parse sub-fields, use "key.sub.grandsub"
})
-// create parser lambda
+// Create parser Lambda
parserLambda := compose.MessageParser(parser)
-// use in Chain
+// Use in Chain
chain := compose.NewChain[*schema.Message, *MyStruct]()
chain.AppendLambda(parserLambda)
-// example
+// Usage example
runner, err := chain.Compile(context.Background())
parsed, err := runner.Invoke(context.Background(), &schema.Message{
Content: `{"id": 1}`,
@@ -183,10 +216,10 @@ parsed, err := runner.Invoke(context.Background(), &schema.Message{
// parsed.ID == 1
```
-Parsing from tool call results is also supported:
+MessageParser supports parsing data from message content (Content) or tool call results (ToolCall), which is commonly used in intent recognition scenarios:
```go
-// parse from tool call results
+// Parse from tool call results
parser := schema.NewMessageJSONParser[*MyStruct](&schema.MessageJSONParseConfig{
ParseFrom: schema.MessageParseFromToolCall,
})
diff --git a/content/en/docs/eino/core_modules/components/tools_node_guide/_index.md b/content/en/docs/eino/core_modules/components/tools_node_guide/_index.md
index 68aaadde243..308016c0311 100644
--- a/content/en/docs/eino/core_modules/components/tools_node_guide/_index.md
+++ b/content/en/docs/eino/core_modules/components/tools_node_guide/_index.md
@@ -1,31 +1,31 @@
---
Description: ""
-date: "2026-01-23"
+date: "2026-03-02"
lastmod: ""
tags: []
title: 'Eino: ToolsNode & Tool Guide'
-weight: 3
+weight: 9
---
## Introduction
-`Tool` in Eino is an external capability that a `ChatModel` may choose to call, including local functions, MCP server tools, etc.
+`Tool` in the Eino framework is defined as "external capabilities that ChatModel can choose to call", including local functions, MCP server tools, etc.
-`ToolsNode` is the designated tool executor in Eino. Whether inside a Graph or in an Agent, tool execution is performed via `ToolsNode`:
+`ToolsNode` is the designated "Tool executor" in the Eino framework. Whether inside a Graph or in an Agent, tool execution is performed via ToolsNode:
```go
// compose/tool_node.go
-// run tools using Invoke
+// run tools using `Invoke`
func (tn *ToolsNode) Invoke(ctx context.Context, input *schema.Message,
opts ...ToolsNodeOption) ([]*schema.Message, error)
-// run tools using Stream
+// run tools using `Stream`
func (tn *ToolsNode) Stream(ctx context.Context, input *schema.Message,
opts ...ToolsNodeOption) (*schema.StreamReader[[]*schema.Message], error)
```
-Configure `ToolsNode` with a list of tools and supporting policies:
+Configure ToolsNode with a list of tools and supporting policies:
```go
// compose/tool_node.go
@@ -43,9 +43,9 @@ type ToolsNodeConfig struct {
}
```
-With this, `ToolsNode` can execute configured tools and gain extensibility such as execution ordering, error handling, argument processing, and middleware.
+With this, ToolsNode "can execute configured tools" and gains extensibility such as execution ordering, error handling, argument processing, and middleware extensions.
-How does `ToolsNode` decide which tool to run? It does not decide; it executes according to the incoming `*schema.Message`:
+How does ToolsNode "decide" which Tool to execute? It doesn't decide - it executes according to the incoming `*schema.Message`:
```go
// schema/message.go
@@ -54,38 +54,56 @@ type Message struct {
// role should be 'assistant' for tool call message
Role RoleType `json:"role"`
- // each ToolCall is generated by ChatModel and to be executed by ToolsNode
+ // here each `ToolCall` is generated by ChatModel and to be executed by ToolsNode
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
+
// other fields...
}
-// ToolCall in a message (used in Assistant when tool calls should be made)
+// ToolCall is the tool call in a message.
+// It's used in Assistant Message when there are tool calls should be made.
type ToolCall struct {
- // Index helps identify the chunk when multiple calls exist (for merging in stream mode)
+ // Index is used when there are multiple tool calls in a message.
+ // In stream mode, it's used to identify the chunk of the tool call for merging.
Index *int `json:"index,omitempty"`
- // ID identifies the specific tool call
+ // ID is the id of the tool call, it can be used to identify the specific tool call.
ID string `json:"id"`
- // Type of the tool call, default "function"
+ // Type is the type of the tool call, default is "function".
Type string `json:"type"`
- // Function payload
+ // Function is the function call to be made.
Function FunctionCall `json:"function"`
- // Extra information
+
+ // Extra is used to store extra information for the tool call.
Extra map[string]any `json:"extra,omitempty"`
}
+// FunctionCall is the function call in a message.
+// It's used in Assistant Message.
type FunctionCall struct {
+ // Name is the name of the function to call, it can be used to identify the specific function.
Name string `json:"name,omitempty"`
+ // Arguments is the arguments to call the function with, in JSON format.
Arguments string `json:"arguments,omitempty"`
}
```
-`ChatModel` generates a list of `ToolCall`s (name, arguments, etc.) and places them in a `*schema.Message` for `ToolsNode`. `ToolsNode` executes each `ToolCall` in turn. If `ExecuteSequentially` is set, tools are executed in the order of the `ToolCalls`. Each result is wrapped into a `*schema.Message` and returned as part of `ToolsNode` output.
+ChatModel (LLM) generates the []ToolCall to be called (including ToolName, Argument, etc.) and places them in a *schema.Message to pass to ToolsNode. ToolsNode actually executes a call for each ToolCall.
+
+If ExecuteSequentially is configured, ToolsNode will execute tools in the order they appear in []ToolCall.
+
+The result of each ToolCall execution is wrapped into a *schema.Message as part of the ToolsNode output.
+
+## Tool Definition
-## Interfaces
+### Interface Definition
-Tool interfaces have three levels:
+The Tool component provides two types of interfaces: **Standard Tool Interface** and **Enhanced Tool Interface**.
-> Code: `eino/compose/tool/interface.go`
+> Code location: eino/components/tool/interface.go
+
+#### Standard Tool Interface
+
+Standard tool interfaces return string type results:
```go
// Basic tool interface, provides tool information
@@ -93,81 +111,181 @@ type BaseTool interface {
Info(ctx context.Context) (*schema.ToolInfo, error)
}
-// Invokable tool, supports synchronous calls
+// Invokable tool interface, supports synchronous calls
type InvokableTool interface {
BaseTool
InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
}
-// Streamable tool, supports streaming output
+// Tool interface supporting streaming output
type StreamableTool interface {
BaseTool
StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
}
```
-### Info
+#### Enhanced Tool Interface
+
+Enhanced tool interfaces support returning structured multimodal results (`*schema.ToolResult`), which can contain text, images, audio, video, and files:
+
+```go
+// EnhancedInvokableTool is a tool interface that supports returning structured multimodal results
+// Unlike InvokableTool which returns a string, this interface returns *schema.ToolResult
+// which can contain text, images, audio, video, and files
+type EnhancedInvokableTool interface {
+ BaseTool
+ InvokableRun(ctx context.Context, toolArgument *schema.ToolArgument, opts ...Option) (*schema.ToolResult, error)
+}
+
+// EnhancedStreamableTool is a streaming tool interface that supports returning structured multimodal results
+// Provides a streaming reader for incremental access to multimodal content
+type EnhancedStreamableTool interface {
+ BaseTool
+ StreamableRun(ctx context.Context, toolArgument *schema.ToolArgument, opts ...Option) (*schema.StreamReader[*schema.ToolResult], error)
+}
+```
+
+### Enhanced Tool Data Structures
+
+> Code location: eino/schema/message.go
+
+#### ToolArgument - Tool Input Parameters
+
+```go
+// ToolArgument contains the input information for a tool call
+type ToolArgument struct {
+ // TextArgument contains the tool call parameters in JSON format
+ TextArgument string
+}
+```
+
+#### ToolResult - Tool Output Result
+
+```go
+// ToolResult represents the structured multimodal output of tool execution
+// Used when a tool needs to return more than just a simple string,
+// such as images, files, or other structured data
+type ToolResult struct {
+ // Parts contains multimodal output parts. Each part can be a different type of content,
+ // such as text, images, or files
+ Parts []ToolOutputPart `json:"parts,omitempty"`
+}
+```
+
+#### ToolOutputPart - Output Content Part
+
+```go
+// ToolPartType defines the content type of a tool output part
+type ToolPartType string
+
+const (
+ ToolPartTypeText ToolPartType = "text" // Text
+ ToolPartTypeImage ToolPartType = "image" // Image
+ ToolPartTypeAudio ToolPartType = "audio" // Audio
+ ToolPartTypeVideo ToolPartType = "video" // Video
+ ToolPartTypeFile ToolPartType = "file" // File
+)
+
+// ToolOutputPart represents a part of the tool execution output
+type ToolOutputPart struct {
+ Type ToolPartType `json:"type"` // Content type
+ Text string `json:"text,omitempty"` // Text content
+ Image *ToolOutputImage `json:"image,omitempty"` // Image content
+ Audio *ToolOutputAudio `json:"audio,omitempty"` // Audio content
+ Video *ToolOutputVideo `json:"video,omitempty"` // Video content
+ File *ToolOutputFile `json:"file,omitempty"` // File content
+ Extra map[string]any `json:"extra,omitempty"` // Extra information
+}
+
+// Multimedia content structs, all containing URL or Base64 data and MIME type information
+type ToolOutputImage struct { MessagePartCommon }
+type ToolOutputAudio struct { MessagePartCommon }
+type ToolOutputVideo struct { MessagePartCommon }
+type ToolOutputFile struct { MessagePartCommon }
+```
+
+### Method Descriptions
+
+#### Info Method
+
+- Purpose: Get tool description information
+- Parameters:
+ - ctx: Context object
+- Returns:
+ - `*schema.ToolInfo`: Tool description information
+ - error: Errors during information retrieval
+
+#### InvokableRun Method (Standard Tool)
+
+- Purpose: Synchronously execute the tool
+- Parameters:
+ - ctx: Context object for passing request-level information and Callback Manager
+ - `argumentsInJSON`: JSON format parameter string
+ - opts: Tool execution options
+- Returns:
+ - string: Execution result
+ - error: Errors during execution
+
+#### InvokableRun Method (Enhanced Tool)
-- Purpose: return tool description for the model
-- Params:
- - `ctx`: context
+- Purpose: Synchronously execute the tool, returning multimodal results
+- Parameters:
+ - ctx: Context object
+ - `toolArgument`: `*schema.ToolArgument` containing JSON format parameters
+ - opts: Tool execution options
- Returns:
- - `*schema.ToolInfo`: tool metadata (name/desc/params)
- - `error`
+ - `*schema.ToolResult`: Execution result containing multimodal content
+ - error: Errors during execution
-### InvokableRun
+#### StreamableRun Method (Standard Tool)
-- Purpose: synchronous tool execution
-- Params:
- - `ctx`: context (request-scoped; carries callback manager)
- - `argumentsInJSON`: JSON string of arguments
- - `opts`: tool options
+- Purpose: Execute tool in streaming mode
+- Parameters:
+ - ctx: Context object
+ - `argumentsInJSON`: JSON format parameter string
+ - opts: Tool execution options
- Returns:
- - `string`: execution result
- - `error`
+ - `*schema.StreamReader[string]`: Streaming execution result
+ - error: Errors during execution
-### StreamableRun
+#### StreamableRun Method (Enhanced Tool)
-- Purpose: streaming tool execution
-- Params:
- - `ctx`: context (request-scoped; carries callback manager)
- - `argumentsInJSON`: JSON string of arguments
- - `opts`: tool options
+- Purpose: Execute tool in streaming mode, returning multimodal result stream
+- Parameters:
+ - ctx: Context object
+ - `toolArgument`: `*schema.ToolArgument` containing JSON format parameters
+ - opts: Tool execution options
- Returns:
- - `*schema.StreamReader[string]`: streaming result
- - `error`
+ - `*schema.StreamReader[*schema.ToolResult]`: Streaming multimodal execution result
+ - error: Errors during execution
-## ToolInfo
+### ToolInfo Struct
-> Code: `eino/schema/tool.go`
+> Code location: eino/schema/tool.go
```go
type ToolInfo struct {
- // Unique tool name, clearly expressing its purpose
+ // Unique tool name that clearly expresses its purpose
Name string
- // Guidance for the model on how/when/why to use the tool
- // You can include brief examples in the description
+ // Used to tell the model how/when/why to use this tool
+ // Can include brief examples in the description
Desc string
- // Definition of accepted parameters
- // Two ways to describe:
- // 1. ParameterInfo: schema.NewParamsOneOfByParams(params)
- // 2. OpenAPI v3: schema.NewParamsOneOfByOpenAPIV3(openAPIV3)
+ // Definition of parameters the tool accepts
+ // Can be described in two ways:
+ // 1. Using ParameterInfo: schema.NewParamsOneOfByParams(params)
+ // 2. Using OpenAPIV3: schema.NewParamsOneOfByOpenAPIV3(openAPIV3)
*ParamsOneOf
}
```
-- Name: unique tool name
-- Desc: guidance for when/how/why to use the tool (can include brief examples)
-- ParamsOneOf: define accepted parameters in one of two ways:
- - ParameterInfo: `schema.NewParamsOneOfByParams(params)`
- - OpenAPI v3: `schema.NewParamsOneOfByOpenAPIV3(openAPIV3)`
+### Common Options
-## Options
-
-`ToolOption` configures tool behavior. ToolsNode has no global options; implementations define specific options via `WrapToolImplSpecificOptFn`.
+The Tool component uses ToolOption to define optional parameters. ToolsNode has no abstracted common options. Each specific implementation can define its own specific Options, wrapped into the unified ToolOption type using the WrapToolImplSpecificOptFn function.
## Usage
+### Standard Tool Usage
+
```go
import (
"github.com/cloudwego/eino/components/tool"
@@ -175,19 +293,21 @@ import (
"github.com/cloudwego/eino/schema"
)
+// Create tools node
toolsNode := compose.NewToolsNode([]tool.Tool{
searchTool, // Search tool
weatherTool, // Weather query tool
calculatorTool, // Calculator tool
})
+// Mock LLM output as input
input := &schema.Message{
Role: schema.Assistant,
ToolCalls: []schema.ToolCall{
{
Function: schema.FunctionCall{
Name: "weather",
- Arguments: `{"city": "深圳", "date": "tomorrow"}`,
+ Arguments: `{"city": "Shenzhen", "date": "tomorrow"}`,
},
},
},
@@ -196,9 +316,161 @@ input := &schema.Message{
toolMessages, err := toolsNode.Invoke(ctx, input)
```
-ToolsNode is typically used after a `ChatModel` inside orchestration.
+### Enhanced Tool Usage
+
+Enhanced tools are suitable for scenarios that need to return multimodal content, such as images, audio, video, or files.
+
+#### Method 1: Use InferEnhancedTool for Automatic Inference
+
+```go
+import (
+ "context"
+
+ "github.com/cloudwego/eino/components/tool"
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+// Define input parameter struct
+type ImageSearchInput struct {
+ Query string `json:"query" jsonschema:"description=Search keyword"`
+}
+
+// Create enhanced tool
+imageSearchTool, err := utils.InferEnhancedTool(
+ "image_search",
+ "Search and return related images",
+ func(ctx context.Context, input *ImageSearchInput) (*schema.ToolResult, error) {
+ // Execute image search logic...
+ imageURL := "https://example.com/image.png"
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "Found the following images:"},
+ {
+ Type: schema.ToolPartTypeImage,
+ Image: &schema.ToolOutputImage{
+ MessagePartCommon: schema.MessagePartCommon{
+ URL: &imageURL,
+ },
+ },
+ },
+ },
+ }, nil
+ },
+)
+```
+
+#### Method 2: Use NewEnhancedTool for Manual Creation
+
+```go
+import (
+ "context"
+
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type FileGeneratorInput struct {
+ FileName string `json:"file_name"`
+ Content string `json:"content"`
+}
+
+// Manually define ToolInfo
+toolInfo := &schema.ToolInfo{
+ Name: "file_generator",
+ Desc: "Generate and return a file",
+ ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
+ "file_name": {Type: "string", Desc: "File name"},
+ "content": {Type: "string", Desc: "File content"},
+ }),
+}
+
+// Create enhanced tool
+fileGenTool := utils.NewEnhancedTool[*FileGeneratorInput](
+ toolInfo,
+ func(ctx context.Context, input *FileGeneratorInput) (*schema.ToolResult, error) {
+ fileURL := "https://example.com/files/" + input.FileName
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "File generated: " + input.FileName},
+ {
+ Type: schema.ToolPartTypeFile,
+ File: &schema.ToolOutputFile{
+ MessagePartCommon: schema.MessagePartCommon{
+ URL: &fileURL,
+ MIMEType: "text/plain",
+ },
+ },
+ },
+ },
+ }, nil
+ },
+)
+```
+
+#### Method 3: Implement EnhancedInvokableTool Interface
+
+```go
+import (
+ "context"
+
+ "github.com/cloudwego/eino/components/tool"
+ "github.com/cloudwego/eino/schema"
+)
+
+type MyEnhancedTool struct {
+ info *schema.ToolInfo
+}
+
+func (t *MyEnhancedTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
+ return t.info, nil
+}
+
+func (t *MyEnhancedTool) InvokableRun(ctx context.Context, toolArgument *schema.ToolArgument, opts ...tool.Option) (*schema.ToolResult, error) {
+ // Parse arguments
+ // toolArgument.TextArgument contains JSON format parameters
+
+ // Execute tool logic...
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "Execution result"},
+ },
+ }, nil
+}
+```
-### In Orchestration
+#### Enhanced Streaming Tool
+
+```go
+import (
+ "context"
+
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type StreamInput struct {
+ Query string `json:"query"`
+}
+
+// Create enhanced streaming tool
+streamTool, err := utils.InferEnhancedStreamTool(
+ "stream_search",
+ "Streaming search tool",
+ func(ctx context.Context, input *StreamInput) (*schema.StreamReader[*schema.ToolResult], error) {
+ results := []*schema.ToolResult{
+ {Parts: []schema.ToolOutputPart{{Type: schema.ToolPartTypeText, Text: "Searching..."}}},
+ {Parts: []schema.ToolOutputPart{{Type: schema.ToolPartTypeText, Text: "Found results"}}},
+ }
+ return schema.StreamReaderFromArray(results), nil
+ },
+)
+```
+
+### Usage in Orchestration
```go
import (
@@ -207,26 +479,41 @@ import (
"github.com/cloudwego/eino/schema"
)
-// Chain
+// Create tools node
+toolsNode, _ := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
+ Tools: []tool.BaseTool{
+ searchTool, // Search tool
+ weatherTool, // Weather query tool
+ calculatorTool, // Calculator tool
+ },
+ })
+
+// In Chain
chain := compose.NewChain[*schema.Message, []*schema.Message]()
chain.AppendToolsNode(toolsNode)
-// Graph
+// In Graph
graph := compose.NewGraph[*schema.Message, []*schema.Message]()
-graph.AddToolsNode(toolsNode)
+graph.AddToolsNode("tools", toolsNode)
```
+> **Note**: When a tool implements both standard and enhanced interfaces, ToolsNode will prioritize using the enhanced interface.
+
## Option Mechanism
+Custom Tools can implement specific Options as needed:
+
```go
import "github.com/cloudwego/eino/components/tool"
+// Define Option struct
type MyToolOptions struct {
Timeout time.Duration
MaxRetries int
RetryInterval time.Duration
}
+// Define Option function
func WithTimeout(timeout time.Duration) tool.Option {
return tool.WrapImplSpecificOptFn(func(o *MyToolOptions) {
o.Timeout = timeout
@@ -234,7 +521,72 @@ func WithTimeout(timeout time.Duration) tool.Option {
}
```
-## Callbacks
+## Middleware Mechanism
+
+ToolsNode supports intercepting and enhancing tool calls through Middleware. There are four types of Middleware:
+
+```go
+// compose/tool_node.go
+
+// ToolMiddleware combines middleware hooks for invokable and streamable tool calls
+type ToolMiddleware struct {
+ // Invokable is for non-streaming standard tool calls
+ Invokable InvokableToolMiddleware
+
+ // Streamable is for streaming standard tool calls
+ Streamable StreamableToolMiddleware
+
+ // EnhancedInvokable is for non-streaming enhanced tool calls
+ EnhancedInvokable EnhancedInvokableToolMiddleware
+
+ // EnhancedStreamable is for streaming enhanced tool calls
+ EnhancedStreamable EnhancedStreamableToolMiddleware
+}
+```
+
+### Middleware Usage Example
+
+```go
+import (
+ "context"
+ "fmt"
+
+ "github.com/cloudwego/eino/compose"
+ "github.com/cloudwego/eino/schema"
+)
+
+// Create ToolsNode with Middleware
+toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
+ Tools: []tool.BaseTool{myEnhancedTool},
+ ToolCallMiddlewares: []compose.ToolMiddleware{
+ {
+ // Standard tool middleware
+ Invokable: func(next compose.InvokableToolEndpoint) compose.InvokableToolEndpoint {
+ return func(ctx context.Context, input *compose.ToolInput) (*compose.ToolOutput, error) {
+ fmt.Printf("Calling standard tool: %s\n", input.Name)
+ return next(ctx, input)
+ }
+ },
+ // Enhanced tool middleware
+ EnhancedInvokable: func(next compose.EnhancedInvokableToolEndpoint) compose.EnhancedInvokableToolEndpoint {
+ return func(ctx context.Context, input *compose.ToolInput) (*compose.EnhancedInvokableToolOutput, error) {
+ fmt.Printf("Calling enhanced tool: %s\n", input.Name)
+ output, err := next(ctx, input)
+ if err != nil {
+ return nil, err
+ }
+ fmt.Printf("Enhanced tool returned %d content parts\n", len(output.Result.Parts))
+ return output, nil
+ }
+ },
+ },
+ },
+})
+```
+
+## Option and Callback Usage
+
+### Callback Usage Example
```go
import (
@@ -300,21 +652,66 @@ result, err := runnable.Invoke(ctx, input, compose.WithCallbacks(helper))
In the tool function body or tool callback handler, you can use `compose.GetToolCallID(ctx)` to get the current Tool's ToolCallID.
-## Implementations
+## Existing Implementations
+
+1. Google Search Tool: Tool implementation based on Google search [Tool - Googlesearch](/docs/eino/ecosystem_integration/tool/tool_googlesearch)
+2. DuckDuckGo Search Tool: Tool implementation based on DuckDuckGo search [Tool - DuckDuckGoSearch](/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search)
+3. MCP: Use MCP server as tool [Tool - MCP](/docs/eino/ecosystem_integration/tool/tool_mcp)
+
+### v0.5.x → v0.6.x
+
+Based on the following two considerations:
+
+1. Major model vendor APIs and MCP Tool protocols specify using JSONSchema to describe tool input/output schemas.
+2. The getkin/kin-openapi@v0.118.0 referenced by Eino has security issues, and secure versions of kin-openapi have incompatible updates.
+
+Eino has removed all OpenAPI schema 3.0 related definitions and methods, switching to JSONSchema 2020-12. For specific removed and added definitions and methods, see [https://github.com/cloudwego/eino/discussions/397](https://github.com/cloudwego/eino/discussions/397).
+
+After upgrading, some eino-ext modules may error with "undefined: schema.NewParamsOneOfByOpenAPIV3" - upgrade the erroring eino-ext modules to the latest version.
+
+If schema migration is complex, you can use the helper tool methods provided in [https://github.com/cloudwego/eino/discussions/397](https://github.com/cloudwego/eino/discussions/397).
+
+### v0.6.x New Enhanced Tools
+
+New `EnhancedInvokableTool` and `EnhancedStreamableTool` interfaces have been added, supporting structured multimodal results.
+
+**Main Changes:**
+
+1. **New Tool Interfaces**:
+
+- `EnhancedInvokableTool`: Receives `*schema.ToolArgument`, returns `*schema.ToolResult`
+- `EnhancedStreamableTool`: Receives `*schema.ToolArgument`, returns `*schema.StreamReader[*schema.ToolResult]`
+
+2. **New Tool Helper Functions** (`components/tool/utils/`):
+
+- `InferEnhancedTool`: Automatically infer and create enhanced tool from function
+- `InferEnhancedStreamTool`: Automatically infer and create enhanced streaming tool from function
+- `NewEnhancedTool`: Manually create enhanced tool
+- `NewEnhancedStreamTool`: Manually create enhanced streaming tool
+
+3. **New Data Structures** (`schema/message.go`):
+
+- `ToolPartType`: Tool output content type enum (text, image, audio, video, file)
+- `ToolArgument`: Tool input parameter struct
+- `ToolResult`: Tool multimodal output result struct
+- `ToolOutputPart`: Tool output content part
+- `ToolOutputImage/Audio/Video/File`: Various multimedia output structs
-1. Google Search: [Tool — GoogleSearch](/docs/eino/ecosystem_integration/tool/tool_googlesearch)
-2. DuckDuckGo: [Tool — DuckDuckGoSearch](/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search)
-3. MCP server as tool: [Tool — MCP](/docs/eino/ecosystem_integration/tool/tool_mcp)
+4. **ToolsNode Enhancements**:
-### v0.5.x → v0.6.x Migration
+- Added `EnhancedInvokableToolMiddleware` and `EnhancedStreamableToolMiddleware`
+- Supports mixed use of enhanced tools and standard tools
+- When a tool implements both interfaces, the enhanced interface is prioritized
-Eino removed all OpenAPI 3.0 related definitions and methods and switched to JSONSchema 2020-12 because:
+5. **Callback Enhancements**:
-- Major model vendors and MCP tool protocols specify input/output schemas via JSONSchema
-- `getkin/kin-openapi@v0.118.0` has security issues, and later secure versions introduced breaking changes
+- `CallbackOutput` added `ToolOutput *schema.ToolResult` field for enhanced tool multimodal output
-See details: https://github.com/cloudwego/eino/discussions/397
+**Use Cases:**
-After upgrading, some `eino-ext` modules may error with `undefined: schema.NewParamsOneOfByOpenAPIV3`; upgrade those modules to the latest versions.
+Enhanced tools are suitable for scenarios that need to return rich media content, such as:
-If schema migration is complex, use the helper tooling linked in the discussion above.
+- Image search tools returning found images
+- File generation tools returning generated files
+- Audio/video processing tools returning processed media files
+- Multimodal AI Agent scenarios
diff --git a/content/en/docs/eino/core_modules/components/tools_node_guide/how_to_create_a_tool.md b/content/en/docs/eino/core_modules/components/tools_node_guide/how_to_create_a_tool.md
index 5ce3d7b0ba7..485591ed101 100644
--- a/content/en/docs/eino/core_modules/components/tools_node_guide/how_to_create_a_tool.md
+++ b/content/en/docs/eino/core_modules/components/tools_node_guide/how_to_create_a_tool.md
@@ -1,6 +1,6 @@
---
Description: ""
-date: "2026-01-30"
+date: "2026-03-02"
lastmod: ""
tags: []
title: How to Create a Tool
@@ -9,12 +9,14 @@ weight: 1
## Tool Structure Basics
-An agent calling a tool involves two steps: (1) the LLM constructs parameters according to the tool definition; (2) the tool executes with those parameters. A tool therefore needs:
+An agent calling a tool involves two steps: (1) the LLM constructs parameters according to the tool definition; (2) the tool is actually called.
-- Tool metadata and parameter constraints
-- An execution interface
+These two basic steps require that a tool contains two parts:
-In Eino, any tool must implement `Info()` to return tool metadata:
+- Tool functionality description and parameter information needed to call the tool
+- Interface to call the tool
+
+In Eino, the BaseTool interface requires any tool to have an Info() interface that returns tool information:
```go
type BaseTool interface {
@@ -22,7 +24,9 @@ type BaseTool interface {
}
```
-Execution interfaces depend on whether the result is streaming:
+### Standard Tool Interface
+
+Based on whether the tool's return structure is streaming, tools can be divided into InvokableTool and StreamableTool, also defined as interfaces:
```go
type InvokableTool interface {
@@ -39,13 +43,69 @@ type StreamableTool interface {
}
```
+### Enhanced Tool Interface
+
+In addition to standard tool interfaces, Eino also provides enhanced tool interfaces that support returning structured multimodal results. Enhanced tools are suitable for scenarios that need to return images, audio, video, files, and other rich media content:
+
+```go
+// EnhancedInvokableTool is a tool interface that supports returning structured multimodal results
+// Unlike InvokableTool which returns a string, this interface returns *schema.ToolResult
+// which can contain text, images, audio, video, and files
+type EnhancedInvokableTool interface {
+ BaseTool
+ InvokableRun(ctx context.Context, toolArgument *schema.ToolArgument, opts ...Option) (*schema.ToolResult, error)
+}
+
+// EnhancedStreamableTool is a streaming tool interface that supports returning structured multimodal results
+type EnhancedStreamableTool interface {
+ BaseTool
+ StreamableRun(ctx context.Context, toolArgument *schema.ToolArgument, opts ...Option) (*schema.StreamReader[*schema.ToolResult], error)
+}
+```
+
+#### Enhanced Tool Data Structures
+
+```go
+// ToolArgument contains the input information for a tool call
+type ToolArgument struct {
+ TextArgument string // Tool call parameters in JSON format
+}
+
+// ToolResult represents the structured multimodal output of tool execution
+type ToolResult struct {
+ Parts []ToolOutputPart `json:"parts,omitempty"`
+}
+
+// ToolPartType defines the content type of a tool output part
+type ToolPartType string
+
+const (
+ ToolPartTypeText ToolPartType = "text" // Text
+ ToolPartTypeImage ToolPartType = "image" // Image
+ ToolPartTypeAudio ToolPartType = "audio" // Audio
+ ToolPartTypeVideo ToolPartType = "video" // Video
+ ToolPartTypeFile ToolPartType = "file" // File
+)
+
+// ToolOutputPart represents a part of the tool execution output
+type ToolOutputPart struct {
+ Type ToolPartType `json:"type"`
+ Text string `json:"text,omitempty"`
+ Image *ToolOutputImage `json:"image,omitempty"`
+ Audio *ToolOutputAudio `json:"audio,omitempty"`
+ Video *ToolOutputVideo `json:"video,omitempty"`
+ File *ToolOutputFile `json:"file,omitempty"`
+ Extra map[string]any `json:"extra,omitempty"`
+}
+```
+
## ToolInfo Representations
-In LLM function-call flows, the model must understand whether generated parameters satisfy constraints. Based on developer habits and domain standards, Eino provides two representations: `params map[string]*ParameterInfo` and `JSON Schema`.
+In the LLM function call process, the LLM generates the parameters for the function call, which requires the LLM to understand whether the generated parameters meet constraints. In Eino, based on developer habits and domain standards, two parameter constraint representations are provided: `params map[string]*ParameterInfo` and `*jsonschema.Schema`.
-### 1) `map[string]*ParameterInfo`
+### Method 1 - map[string]*ParameterInfo
-Intuitive map-based parameter descriptions:
+In many developers' intuitive habits, parameters can be described using a map where the key is the parameter name and the value is the detailed constraint for that parameter. Eino defines ParameterInfo to represent a parameter description:
```go
// Full definition: https://github.com/cloudwego/eino/blob/main/schema/tool.go
@@ -59,7 +119,7 @@ type ParameterInfo struct {
}
```
-Example:
+For example, a parameter representing a User can be expressed as:
```go
map[string]*schema.ParameterInfo{
@@ -71,17 +131,19 @@ map[string]*schema.ParameterInfo{
Type: schema.Integer,
},
"gender": &schema.ParameterInfo{
- Type: schema.String,
+ Type: schema.String,
Enum: []string{"male", "female"},
},
}
```
-### 2) JSON Schema
+This representation is very simple and intuitive, commonly used when parameters are manually maintained by developers through coding.
+
+### Method 2 - JSON Schema
-Another common way to represent parameter constraints is `JSON Schema`: [https://json-schema.org/draft/2020-12](https://json-schema.org/draft/2020-12).
+Another common way to represent parameter constraints is JSON Schema ([https://json-schema.org/draft/2020-12](https://json-schema.org/draft/2020-12)).
-The JSON Schema standard provides very rich ways to constrain parameters. In practice, developers generally don't construct this structure themselves, but use methods to generate it.
+The JSON Schema standard provides very rich ways to constrain parameters. In practice, developers generally don't construct this structure themselves but use methods to generate it.
#### Using GoStruct2ParamsOneOf
@@ -91,15 +153,15 @@ Eino provides a way to describe parameter constraints through go tags in structs
func GoStruct2ParamsOneOf[T any](opts ...Option) (*schema.ParamsOneOf, error)
```
-The tags used to extract parameter field names and descriptions from T are as follows:
+The tags used to extract parameter field names and descriptions from T are:
-- jsonschema_description:"xxx" [recommended] or jsonschema:"description=xxx"
+- `jsonschema_description:"xxx"` [recommended] or `jsonschema:"description=xxx"`
- Descriptions often contain commas, and commas in tags are separators for different fields and cannot be escaped. It's strongly recommended to use the separate jsonschema_description tag.
-- jsonschema:"enum=xxx,enum=yyy,enum=zzz"
-- Fields are required by default; json:"xxx,omitempty" => use json tag's omitempty to indicate not required
-- Use utils.WithSchemaModifier to implement custom parsing methods
+- `jsonschema:"enum=xxx,enum=yyy,enum=zzz"`
+- `jsonschema:"required"`
+- `json:"xxx,omitempty"` => can use json tag's omitempty to represent non-required
-Example:
+Use `utils.WithSchemaModifier` to implement custom parsing methods, see the example below:
```go
package main
@@ -110,7 +172,7 @@ import (
)
type User struct {
- Name string `json:"name,omitempty" jsonschema_description=the name of the user`
+ Name string `json:"name" jsonschema_description:"the name of the user" jsonschema:"required"`
Age int `json:"age" jsonschema_description:"the age of the user"`
Gender string `json:"gender" jsonschema:"enum=male,enum=female"`
}
@@ -120,13 +182,17 @@ func main() {
}
```
-This method is generally not called by developers directly; instead, use `utils.GoStruct2ToolInfo()` to build ToolInfo, or use `utils.InferTool()` directly to build a tool. See the "Converting local functions to tools" section below for details.
+This method is generally not called directly by developers. Instead, use `utils.GoStruct2ToolInfo()` to build ToolInfo, or use `utils.InferTool()` directly to build a tool. See the "Converting local functions to tools" section below for details.
## Ways to Implement a Tool
-### Approach 1 — Implement Interfaces Directly
+### Method 1 - Implement Interfaces Directly
-Implement `InvokableTool`:
+Since tool definitions are all interfaces, the most direct way to implement a tool is to implement the interface.
+
+#### Implementing Standard Tool Interface
+
+Using InvokableTool as an example:
```go
type AddUser struct{}
@@ -136,13 +202,13 @@ func (t *AddUser) Info(_ context.Context) (*schema.ToolInfo, error) {
Name: "add_user",
Desc: "add user",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
- // omitted; see earlier for building params constraints
+ // omit, refer to the above method for building params constraints
}),
}, nil
}
func (t *AddUser) InvokableRun(_ context.Context, argumentsInJSON string, _ ...tool.Option) (string, error) {
- // 1. Deserialize argumentsInJSON and handle options
+ // 1. Deserialize argumentsInJSON, handle options, etc.
user, _ := json.Unmarshal([]byte(argumentsInJSON))
// 2. Handle business logic
// 3. Serialize the result to string and return
@@ -151,29 +217,82 @@ func (t *AddUser) InvokableRun(_ context.Context, argumentsInJSON string, _ ...t
}
```
-Because the LLM always supplies a JSON string, the tool receives `argumentsInJSON`; you deserialize it and return a JSON string.
+Since the LLM's function call parameters are always a string, corresponding to the Eino framework, the tool's call parameter input is also a JSON serialized into a string. Therefore, this method requires developers to handle parameter deserialization themselves, and the call result is also returned as a string.
-### Approach 2 — Wrap a Local Function
+#### Implementing Enhanced Tool Interface
-Often you have an existing function (e.g., `AddUser`) and want the LLM to decide when/how to call it. Eino provides `NewTool` for this, and `InferTool` for tag-based parameter constraints.
+When you need to return multimodal content (such as images, audio, video, files, etc.), you can implement the EnhancedInvokableTool interface:
-> See tests in `cloudwego/eino/components/tool/utils/invokable_func_test.go` and `streamable_func_test.go`.
+```go
+type ImageSearchTool struct{}
-#### `NewTool`
+func (t *ImageSearchTool) Info(_ context.Context) (*schema.ToolInfo, error) {
+ return &schema.ToolInfo{
+ Name: "image_search",
+ Desc: "Search and return related images",
+ ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
+ "query": {
+ Type: schema.String,
+ Desc: "Search keyword",
+ Required: true,
+ },
+ }),
+ }, nil
+}
-For functions of signature:
+func (t *ImageSearchTool) InvokableRun(_ context.Context, toolArgument *schema.ToolArgument, _ ...tool.Option) (*schema.ToolResult, error) {
+ // 1. Parse parameters (toolArgument.TextArgument contains JSON format parameters)
+ var input struct {
+ Query string `json:"query"`
+ }
+ json.Unmarshal([]byte(toolArgument.TextArgument), &input)
+
+ // 2. Execute search logic...
+ imageURL := "https://example.com/image.png"
+
+ // 3. Return multimodal result
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "Found the following images:"},
+ {
+ Type: schema.ToolPartTypeImage,
+ Image: &schema.ToolOutputImage{
+ MessagePartCommon: schema.MessagePartCommon{
+ URL: &imageURL,
+ },
+ },
+ },
+ },
+ }, nil
+}
+```
+
+### Method 2 - Convert Local Functions to Tools
+
+During development, we often need to wrap a local function into an Eino tool. For example, our code already has an AddUser method, but to let the LLM autonomously decide how to call this method, we need to turn this method into a tool and bind it to the LLM.
+
+Eino provides the NewTool method to convert a function into a tool. Additionally, for scenarios where parameter constraints are expressed through struct tags, the InferTool method is provided to make the construction process simpler.
+
+For examples of the methods below, refer to unit tests in `cloudwego/eino/components/tool/utils/invokable_func_test.go` and `cloudwego/eino/components/tool/utils/streamable_func_test.go`.
+
+#### Standard Tool: Using NewTool Method
+
+When a function has the following signature, you can use NewTool to turn it into an InvokableTool:
```go
type InvokeFunc[T, D any] func(ctx context.Context, input T) (output D, err error)
```
-Use:
+The NewTool method is:
```go
+// Code: github.com/cloudwego/eino/components/tool/utils/invokable_func.go
func NewTool[T, D any](desc *schema.ToolInfo, i InvokeFunc[T, D], opts ...Option) tool.InvokableTool
```
-Example:
+Similarly, NewStreamTool can create a StreamableTool.
+
+Using AddUser as an example, it can be built as follows:
```go
import (
@@ -210,7 +329,7 @@ func createTool() tool.InvokableTool {
Type: schema.Integer,
},
"gender": &schema.ParameterInfo{
- Type: schema.String,
+ Type: schema.String,
Enum: []string{"male", "female"},
},
},
@@ -221,15 +340,19 @@ func createTool() tool.InvokableTool {
}
```
-#### `InferTool`
+#### Standard Tool: Using InferTool Method
-When parameter constraints live in the input struct tags, use `InferTool`:
+From NewTool, we can see that building a tool requires separately passing ToolInfo and InvokeFunc, where ToolInfo contains the ParamsOneOf part representing the function's input parameter constraints, and InvokeFunc's signature also has an input parameter. This means: the ParamsOneOf part and InvokeFunc's input parameter need to be consistent.
+
+When a function is entirely implemented by developers themselves, developers need to manually maintain the input parameter and ParamsOneOf to keep them consistent. A more elegant solution is "maintaining parameter constraints directly in the input parameter type definition", refer to the GoStruct2ParamsOneOf introduction above.
+
+When parameter constraint information is contained in the input parameter type definition, you can use InferTool to implement it:
```go
func InferTool[T, D any](toolName, toolDesc string, i InvokeFunc[T, D], opts ...Option) (tool.InvokableTool, error)
```
-Example:
+Using AddUser as an example:
```go
import (
@@ -239,9 +362,9 @@ import (
)
type User struct {
- Name string `json:"name" jsonschema:"description=the name of the user"`
- Age int `json:"age,omitempty" jsonschema:"description=the age of the user"`
- Gender string `json:"gender,omitempty" jsonschema:"enum=male,enum=female"`
+ Name string `json:"name" jsonschema:"required,description=the name of the user"`
+ Age int `json:"age" jsonschema:"description=the age of the user"`
+ Gender string `json:"gender" jsonschema:"enum=male,enum=female"`
}
type Result struct {
@@ -257,11 +380,201 @@ func createTool() (tool.InvokableTool, error) {
}
```
-#### Using InferOptionableTool
+#### Enhanced Tool: Using NewEnhancedTool Method
+
+When you need to return multimodal results, you can use the NewEnhancedTool method:
+
+```go
+type EnhancedInvokeFunc[T any] func(ctx context.Context, input T) (output *schema.ToolResult, err error)
+
+func NewEnhancedTool[T any](desc *schema.ToolInfo, i EnhancedInvokeFunc[T], opts ...Option) tool.EnhancedInvokableTool
+```
+
+Example:
+
+```go
+import (
+ "context"
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type ImageSearchInput struct {
+ Query string `json:"query"`
+}
+
+func searchImages(ctx context.Context, input *ImageSearchInput) (*schema.ToolResult, error) {
+ // Execute image search logic...
+ imageURL := "https://example.com/image.png"
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "Found the following images:"},
+ {
+ Type: schema.ToolPartTypeImage,
+ Image: &schema.ToolOutputImage{
+ MessagePartCommon: schema.MessagePartCommon{
+ URL: &imageURL,
+ },
+ },
+ },
+ },
+ }, nil
+}
+
+func createEnhancedTool() tool.EnhancedInvokableTool {
+ return utils.NewEnhancedTool(&schema.ToolInfo{
+ Name: "image_search",
+ Desc: "Search and return related images",
+ ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
+ "query": {Type: schema.String, Desc: "Search keyword", Required: true},
+ }),
+ }, searchImages)
+}
+```
-The Option mechanism is a mechanism provided by Eino for passing dynamic parameters at runtime. For details, see [Eino: CallOption Capabilities and Conventions](/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities). This mechanism also applies to custom tools.
+#### Enhanced Tool: Using InferEnhancedTool Method
-When you need custom option parameters, use `InferOptionableTool`:
+Similar to InferTool, InferEnhancedTool can automatically infer parameter constraints from function signatures:
+
+```go
+func InferEnhancedTool[T any](toolName, toolDesc string, i EnhancedInvokeFunc[T], opts ...Option) (tool.EnhancedInvokableTool, error)
+```
+
+Example:
+
+```go
+import (
+ "context"
+ "github.com/cloudwego/eino/components/tool"
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type ImageSearchInput struct {
+ Query string `json:"query" jsonschema:"required" jsonschema_description:"Search keyword"`
+}
+
+func searchImages(ctx context.Context, input *ImageSearchInput) (*schema.ToolResult, error) {
+ imageURL := "https://example.com/image.png"
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: "Found the following images:"},
+ {
+ Type: schema.ToolPartTypeImage,
+ Image: &schema.ToolOutputImage{
+ MessagePartCommon: schema.MessagePartCommon{
+ URL: &imageURL,
+ },
+ },
+ },
+ },
+ }, nil
+}
+
+func createEnhancedTool() (tool.EnhancedInvokableTool, error) {
+ return utils.InferEnhancedTool("image_search", "Search and return related images", searchImages)
+}
+```
+
+#### Enhanced Streaming Tool: Using InferEnhancedStreamTool Method
+
+For scenarios that need to stream multimodal content, you can use InferEnhancedStreamTool:
+
+```go
+func InferEnhancedStreamTool[T any](toolName, toolDesc string, s EnhancedStreamFunc[T], opts ...Option) (tool.EnhancedStreamableTool, error)
+```
+
+Example:
+
+```go
+import (
+ "context"
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type StreamSearchInput struct {
+ Query string `json:"query" jsonschema:"required"`
+}
+
+func streamSearch(ctx context.Context, input *StreamSearchInput) (*schema.StreamReader[*schema.ToolResult], error) {
+ results := []*schema.ToolResult{
+ {Parts: []schema.ToolOutputPart{{Type: schema.ToolPartTypeText, Text: "Searching..."}}},
+ {Parts: []schema.ToolOutputPart{{Type: schema.ToolPartTypeText, Text: "Found results"}}},
+ }
+ return schema.StreamReaderFromArray(results), nil
+}
+
+func createEnhancedStreamTool() (tool.EnhancedStreamableTool, error) {
+ return utils.InferEnhancedStreamTool("stream_search", "Streaming search tool", streamSearch)
+}
+```
+
+#### Enhanced Tool: Using InferOptionableEnhancedTool Method
+
+When you need custom option parameters, you can use InferOptionableEnhancedTool:
+
+```go
+func InferOptionableEnhancedTool[T any](toolName, toolDesc string, i OptionableEnhancedInvokeFunc[T], opts ...Option) (tool.EnhancedInvokableTool, error)
+```
+
+Example:
+
+```go
+import (
+ "context"
+ "github.com/cloudwego/eino/components/tool"
+ "github.com/cloudwego/eino/components/tool/utils"
+ "github.com/cloudwego/eino/schema"
+)
+
+type ImageSearchOption struct {
+ MaxResults int
+ Quality string
+}
+
+func WithMaxResults(n int) tool.Option {
+ return tool.WrapImplSpecificOptFn(func(o *ImageSearchOption) {
+ o.MaxResults = n
+ })
+}
+
+type ImageSearchInput struct {
+ Query string `json:"query" jsonschema:"required"`
+}
+
+func searchImagesWithOption(ctx context.Context, input *ImageSearchInput, opts ...tool.Option) (*schema.ToolResult, error) {
+ baseOption := &ImageSearchOption{MaxResults: 10, Quality: "high"}
+ option := tool.GetImplSpecificOptions(baseOption, opts...)
+
+ // Use option.MaxResults and option.Quality to execute search...
+ imageURL := "https://example.com/image.png"
+
+ return &schema.ToolResult{
+ Parts: []schema.ToolOutputPart{
+ {Type: schema.ToolPartTypeText, Text: fmt.Sprintf("Returning %d images:", option.MaxResults)},
+ {
+ Type: schema.ToolPartTypeImage,
+ Image: &schema.ToolOutputImage{
+ MessagePartCommon: schema.MessagePartCommon{URL: &imageURL},
+ },
+ },
+ },
+ }, nil
+}
+
+func createOptionableEnhancedTool() (tool.EnhancedInvokableTool, error) {
+ return utils.InferOptionableEnhancedTool("image_search", "Search images", searchImagesWithOption)
+}
+```
+
+#### Using InferOptionableTool Method (Standard Tool)
+
+The Option mechanism is a mechanism provided by Eino for passing dynamic parameters at runtime. For details, see Eino: CallOption Capabilities and Conventions. This mechanism also applies to custom tools.
+
+When developers need custom option parameters, they can use InferOptionableTool. Compared to InferTool's function signature requirements, this method adds an option parameter:
```go
func InferOptionableTool[T, D any](toolName, toolDesc string, i OptionableInvokeFunc[T, D], opts ...Option) (tool.InvokableTool, error)
@@ -309,15 +622,15 @@ func useInInvoke() {
}
```
-### Approach 3 — Use tools from eino-ext
+### Method 3 - Use Tools from eino-ext
-Beyond custom tools that need to be implemented yourself, the eino-ext project has many general-purpose tool implementations that can be used out of the box, such as [Tool - Googlesearch](/docs/eino/ecosystem_integration/tool/tool_googlesearch), [Tool - DuckDuckGoSearch](/docs/eino/ecosystem_integration/tool/tool_duckduckgo_search), wikipedia, httprequest, etc. See various implementations at [https://github.com/cloudwego/eino-ext/tree/main/components/tool](https://github.com/cloudwego/eino-ext/tree/main/components/tool).
+Besides various custom tools that need to be implemented yourself, the eino-ext project has many general-purpose tool implementations that can be used out of the box, such as Tool - Googlesearch, Tool - DuckDuckGoSearch, wikipedia, httprequest, etc. See various implementations at [https://github.com/cloudwego/eino-ext/tree/main/components/tool](https://github.com/cloudwego/eino-ext/tree/main/components/tool).
-### Approach 4 — Use MCP protocol
+### Method 4 - Use MCP Protocol
-MCP (Model Context Protocol) is an open protocol for exposing tool capabilities to LLMs. Eino can treat tools provided via MCP as regular tools, greatly expanding available capabilities.
+MCP (Model Context Protocol) is an open model context protocol. More and more tools and platforms are exposing their capabilities to LLMs based on this protocol. Eino can use tools provided via MCP as regular tools, greatly expanding the variety of tools available.
-Using MCP tools in Eino is straightforward:
+Using MCP-provided tools in Eino is very convenient:
```go
import (
@@ -359,4 +672,8 @@ func getMCPTool(ctx context.Context) []tool.BaseTool {
}
```
-Code reference: https://github.com/cloudwego/eino-ext/blob/main/components/tool/mcp/examples/mcp.go
+Code reference: [https://github.com/cloudwego/eino-ext/blob/main/components/tool/mcp/examples/mcp.go](https://github.com/cloudwego/eino-ext/blob/main/components/tool/mcp/examples/mcp.go)
+
+## Tool Type Selection Guide
+
+> **Note**: When a tool implements both standard and enhanced interfaces, ToolsNode will prioritize using the enhanced interface.
diff --git a/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Ark_Agentkit_Sandbox.md b/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Ark_Agentkit_Sandbox.md
new file mode 100644
index 00000000000..3528a927361
--- /dev/null
+++ b/content/en/docs/eino/core_modules/eino_adk/Eino_ADK_ChatModelAgentMiddleware/Middleware_FileSystem/Backend_Ark_Agentkit_Sandbox.md
@@ -0,0 +1,205 @@
+---
+Description: ""
+date: "2026-03-02"
+lastmod: ""
+tags: []
+title: 'Backend: Ark Agentkit Sandbox'
+weight: 1
+---
+
+## Agentkit Sandbox Backend
+
+Package: `github.com/cloudwego/eino-ext/adk/backend/agentkit`
+
+Note: If your eino version is v0.8.0 or above, you need to use ark agentkit backend [v0.2.0-alpha](https://github.com/cloudwego/eino-ext/releases/tag/adk%2Fbackend%2Fagentkit%2Fv0.2.0-alpha.1) version.
+
+### Overview
+
+Agentkit Sandbox Backend is a remote sandbox implementation of EINO ADK FileSystem that executes file system operations in an isolated cloud environment through Volcengine Agentkit service.
+
+#### Core Features
+
+- Security Isolation - All operations execute in a remote sandbox environment
+- Session Management - Supports session isolation with configurable TTL
+- Request Signing - Automatic AK/SK authentication for Volcengine API
+
+### Installation
+
+```bash
+go get github.com/cloudwego/eino-ext/adk/backend/agentkit
+```
+
+### Configuration
+
+#### Environment Variables
+
+```bash
+export VOLC_ACCESS_KEY_ID="your_access_key"
+export VOLC_SECRET_ACCESS_KEY="your_secret_key"
+export VOLC_TOOL_ID="your_tool_id"
+```
+
+#### Config Structure
+
+```go
+type Config struct {
+ // Required
+ AccessKeyID string // Access Key ID
+ SecretAccessKey string // Access Key Secret
+ ToolID string // Sandbox Tool ID
+
+ // Optional
+ UserSessionID string // User session ID for isolation
+ Region Region // Region, defaults to cn-beijing
+ SessionTTL int // Session TTL (60-86400 seconds)
+ ExecutionTimeout int // Command execution timeout
+ HTTPClient *http.Client // Custom HTTP client
+}
+```
+
+### Quick Start
+
+#### Basic Usage
+
+```go
+import (
+ "context"
+ "os"
+ "time"
+
+ "github.com/cloudwego/eino-ext/adk/backend/agentkit"
+ "github.com/cloudwego/eino/adk/filesystem"
+)
+
+func main() {
+ ctx := context.Background()
+
+ backend, err := agentkit.NewSandboxToolBackend(&agentkit.Config{
+ AccessKeyID: os.Getenv("VOLC_ACCESS_KEY_ID"),
+ SecretAccessKey: os.Getenv("VOLC_SECRET_ACCESS_KEY"),
+ ToolID: os.Getenv("VOLC_TOOL_ID"),
+ UserSessionID: "session-" + time.Now().Format("20060102-150405"),
+ Region: agentkit.RegionOfBeijing,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ // Write file
+ err = backend.Write(ctx, &filesystem.WriteRequest{
+ FilePath: "/home/gem/hello.txt",
+ Content: "Hello, Sandbox!",
+ })
+
+ // Read file
+ content, err := backend.Read(ctx, &filesystem.ReadRequest{
+ FilePath: "/home/gem/hello.txt",
+ })
+}
+```
+
+#### Integration with Agent
+
+```go
+import (
+ "github.com/cloudwego/eino/adk"
+ fsMiddleware "github.com/cloudwego/eino/adk/middlewares/filesystem"
+)
+
+// Create Backend
+backend, _ := agentkit.NewSandboxToolBackend(config)
+
+// Create Middleware
+middleware, _ := fsMiddleware.New(ctx, &fsMiddleware.Config{
+ Backend: backend,
+ Shell: backend,
+})
+
+// Create Agent
+agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
+ Name: "SandboxAgent",
+ Description: "AI Agent with secure file system access capabilities",
+ Model: chatModel,
+ Handlers: []adk.ChatModelAgentMiddleware{middleware},
+})
+```
+
+### API Reference
+
+
+| Field | Type | Description |
+Name | string | Unique identifier for the Skill. Agent calls the Skill by this name. Recommend using short, meaningful names (e.g., pdf-processing , web-research ). Corresponds to the name field in SKILL.md frontmatter |
+Description | string | Description of Skill functionality. This is the key basis for Agent to decide whether to use the Skill, should clearly explain applicable scenarios and capabilities. Corresponds to the description field in SKILL.md frontmatter |
+Context | ContextMode | Context mode. Possible values: fork (copy history messages to create new Agent for execution), isolate (create new Agent with isolated context for execution). Leave empty for inline mode (directly return Skill content) |
+Agent | string | Specify the Agent name to use. Used with Context field, gets the corresponding Agent factory function via AgentHub . Leave empty to use default Agent |
+Model | string | Specify the model name to use. Gets the corresponding model instance via ModelHub . In Context mode, passed to Agent factory; in inline mode, switches the model used by subsequent ChatModel calls |
+
+
+### ContextMode
+
+```go
+const (
+ ContextModeFork ContextMode = "fork" // Copy history messages
+ ContextModeIsolate ContextMode = "isolate" // Isolate context
+)
+```
+
+
+| Field | Type | Description |
+FrontMatter | FrontMatter | Embedded metadata structure, including Name , Description , Context , Agent , Model |
+Content | string | Body content after frontmatter in SKILL.md file. Contains detailed instructions, workflows, examples, etc. Agent reads this content after activating Skill |
+BaseDirectory | string | Absolute path of Skill directory. Agent can use this path to access other resource files in the Skill directory (such as scripts, templates, reference docs, etc.) |
+
+
+## Backend
+
+Skill backend interface, defining how skills are retrieved. The Backend interface decouples skill storage from usage, providing the following benefits:
+
+- **Flexible storage**: Skills can be stored in local file system, database, remote service, cloud storage, etc.
+- **Extensibility**: Teams can implement custom Backends as needed, such as dynamically loading from Git repository, getting from config center, etc.
+- **Test friendly**: Can easily create Mock Backend for unit testing
+
+```go
+type Backend interface {
+ List(ctx context.Context) ([]FrontMatter, error)
+ Get(ctx context.Context, name string) (Skill, error)
+}
+```
+
+
+| Field | Type | Required | Default | Description |
+| Model | model.BaseChatModel | Yes | | Chat model used for generating summaries |
+| ModelOptions | []model.Option | No | | Options passed to the model when generating summaries |
+| TokenCounter | TokenCounterFunc | No | ~4 chars/token | Custom token counting function |
+| Trigger | *TriggerCondition | No | 190,000 tokens | Condition to trigger summarization |
+| Instruction | string | No | Built-in prompt | Custom summarization instruction |
+| TranscriptFilePath | string | No | | Full conversation transcript file path |
+| Prepare | PrepareFunc | No | | Custom preprocessing function before summary generation |
+| Finalize | FinalizeFunc | No | | Custom post-processing function for final messages |
+| Callback | CallbackFunc | No | | Called after Finalize to observe state changes (read-only) |
+| EmitInternalEvents | bool | No | false | Whether to emit internal events |
+| PreserveUserMessages | *PreserveUserMessages | No | Enabled: true | Whether to preserve original user messages in summary |
+
+
+### TriggerCondition Structure
+
+```go
+type TriggerCondition struct {
+ // ContextTokens triggers summarization when total token count exceeds this threshold
+ ContextTokens int
+}
+```
+
+### PreserveUserMessages Structure
+
+```go
+type PreserveUserMessages struct {
+ // Enabled whether to enable user message preservation
+ Enabled bool
+
+ // MaxTokens maximum tokens for preserved user messages
+ // Only preserves the most recent user messages until this limit is reached
+ // Defaults to 1/3 of TriggerCondition.ContextTokens
+ MaxTokens int
+}
+```
+
+### Configuration Examples
+
+**Custom Token Threshold**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ Trigger: &summarization.TriggerCondition{
+ ContextTokens: 100000, // Trigger at 100k tokens
+ },
+})
+```
+
+**Custom Token Counter**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ TokenCounter: func(ctx context.Context, input *summarization.TokenCounterInput) (int, error) {
+ // Use your tokenizer
+ return yourTokenizer.Count(input.Messages)
+ },
+})
+```
+
+**Set Transcript File Path**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ TranscriptFilePath: "/path/to/transcript.txt",
+})
+```
+
+**Custom Finalize Function**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ Finalize: func(ctx context.Context, originalMessages []adk.Message, summary adk.Message) ([]adk.Message, error) {
+ // Custom logic to build final messages
+ return []adk.Message{
+ schema.SystemMessage("Your system prompt"),
+ summary,
+ }, nil
+ },
+})
+```
+
+**Using Callback to Observe State Changes/Store**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ Callback: func(ctx context.Context, before, after adk.ChatModelAgentState) error {
+ log.Printf("Summarization completed: %d messages -> %d messages",
+ len(before.Messages), len(after.Messages))
+ return nil
+ },
+})
+```
+
+**Control User Message Preservation**
+
+```go
+mw, err := summarization.New(ctx, &summarization.Config{
+ Model: yourChatModel,
+ PreserveUserMessages: &summarization.PreserveUserMessages{
+ Enabled: true,
+ MaxTokens: 50000, // Preserve up to 50k tokens of user messages
+ },
+})
+```
+
+## How It Works
+
+```mermaid
+flowchart TD
+ A[BeforeModelRewriteState] --> B{Token count exceeds threshold?}
+ B -->|No| C[Return original state]
+ B -->|Yes| D[Emit BeforeSummary event]
+ D --> E{Has custom Prepare?}
+ E -->|Yes| F[Call Prepare]
+ E -->|No| G[Call model to generate summary]
+ F --> G
+ G --> H{Has custom Finalize?}
+ H -->|Yes| I[Call Finalize]
+ H -->|No| L{Has custom Callback?}
+ I --> L
+ L -->|Yes| M[Call Callback]
+ L -->|No| J[Emit AfterSummary event]
+ M --> J
+ J --> K[Return new state]
+
+ style A fill:#e3f2fd
+ style G fill:#fff3e0
+ style D fill:#e8f5e9
+ style J fill:#e8f5e9
+ style K fill:#c8e6c9
+ style C fill:#f5f5f5
+ style M fill:#fce4ec
+ style F fill:#fff3e0
+ style I fill:#fff3e0
+```
+
+## Internal Events
+
+When EmitInternalEvents is set to true, the middleware emits events at key points:
+
+
+| Method | Input | Output | Scope |
+BeforeAgent | Agent runtime env (*ChatModelAgentContext ) | Modified Agent runtime env | Entire Run lifecycle, called only once |
+BeforeModelRewriteState | Persistent state + Model runtime env | Modified persistent state | Persistent state across iterations (message list) |
+WrapModel | ChatModel being wrapped + Model runtime env | Wrapped Model | Single Model request input, output and config |
+AfterModelRewriteState | Persistent state (with response) + Model runtime env | Modified persistent state | Persistent state across iterations (message list) |
+WrapInvokableToolCall | Tool being wrapped + Tool runtime env | Wrapped endpoint | Single Tool request input, output and config |
+WrapStreamableToolCall | Tool being wrapped + Tool runtime env | Wrapped endpoint | Single Tool request input, output and config |
+
+
+---
+
+## Execution Order
+
+### Model Call Lifecycle (wrapper chain from outer to inner)
+
+1. `AgentMiddleware.BeforeChatModel` (hook, runs before model call)
+2. `ChatModelAgentMiddleware.BeforeModelRewriteState` (hook, can modify state before model call)
+3. `retryModelWrapper` (internal - retries on failure, if configured)
+4. `eventSenderModelWrapper` preprocessing (internal - prepares event sending)
+5. `ChatModelAgentMiddleware.WrapModel` preprocessing (wrapper, wrapped at request time, first registered runs first)
+6. `callbackInjectionModelWrapper` (internal - injects callbacks if not enabled)
+7. `Model.Generate/Stream`
+8. `callbackInjectionModelWrapper` postprocessing
+9. `ChatModelAgentMiddleware.WrapModel` postprocessing (wrapper, first registered runs last)
+10. `eventSenderModelWrapper` postprocessing (internal - sends model response event)
+11. `retryModelWrapper` postprocessing (internal - handles retry logic)
+12. `ChatModelAgentMiddleware.AfterModelRewriteState` (hook, can modify state after model call)
+13. `AgentMiddleware.AfterChatModel` (hook, runs after model call)
+
+### Tool Call Lifecycle (from outer to inner)
+
+1. `eventSenderToolHandler` (internal ToolMiddleware - sends tool result event after all processing)
+2. `ToolsConfig.ToolCallMiddlewares` (ToolMiddleware)
+3. `AgentMiddleware.WrapToolCall` (ToolMiddleware)
+4. `ChatModelAgentMiddleware.WrapInvokableToolCall/WrapStreamableToolCall` (wrapped at request time, first registered is outermost)
+5. `Tool.InvokableRun/StreamableRun`
+
+---
+
+## Migration Guide
+
+### Migrating from AgentMiddleware to ChatModelAgentMiddleware
+
+**Before (AgentMiddleware):**
+
+```go
+middleware := adk.AgentMiddleware{
+ BeforeChatModel: func(ctx context.Context, state *adk.ChatModelAgentState) error {
+ return nil
+ },
+}
+```
+
+**After (ChatModelAgentMiddleware):**
+
+```go
+type MyHandler struct {
+ *adk.BaseChatModelAgentMiddleware
+}
+
+func (h *MyHandler) BeforeModelRewriteState(ctx context.Context, state *adk.ChatModelAgentState, mc *adk.ModelContext) (context.Context, *adk.ChatModelAgentState, error) {
+ newCtx := context.WithValue(ctx, myKey, myValue)
+ return newCtx, state, nil
+}
+```
+
+### Migrating from compose.ProcessState[*State]
+
+**Before:**
+
+```go
+compose.ProcessState(ctx, func(_ context.Context, st *adk.State) error {
+ st.Extra["myKey"] = myValue
+ return nil
+})
+```
+
+**After (using SetRunLocalValue/GetRunLocalValue):**
+
+```go
+if err := adk.SetRunLocalValue(ctx, "myKey", myValue); err != nil {
+ return ctx, state, err
+}
+
+if val, found, err := adk.GetRunLocalValue(ctx, "myKey"); err == nil && found {
+}
+```
diff --git a/content/en/docs/eino/core_modules/eino_adk/adk_agent_callback.md b/content/en/docs/eino/core_modules/eino_adk/adk_agent_callback.md
new file mode 100644
index 00000000000..c75581d8489
--- /dev/null
+++ b/content/en/docs/eino/core_modules/eino_adk/adk_agent_callback.md
@@ -0,0 +1,345 @@
+---
+Description: ""
+date: "2026-03-02"
+lastmod: ""
+tags: []
+title: 'Eino ADK: Agent Callback'
+weight: 9
+---
+
+This feature adds Callback support to ADK Agents, similar to the callback mechanism in the compose package. Through callbacks, users can observe the Agent's execution lifecycle for logging, tracing, monitoring, and other purposes.
+
+## Overview
+
+The ADK Agent Callback mechanism shares the same infrastructure as the callback system in Eino compose:
+
+- Uses the same `callbacks.Handler` interface
+- Uses the same `callbacks.RunInfo` structure
+- Can be combined with other component callbacks (such as ChatModel, Tool, etc.)
+
+> 💡
+> Through Agent Callback, you can hook into key points of Agent execution to implement observability capabilities like tracing, logging, and metrics. This feature was introduced in [v0.8.0.Beta](https://github.com/cloudwego/eino/releases/tag/v0.8.0-beta.1).
+
+## Core Types
+
+### ComponentOfAgent
+
+Component type identifier used to identify Agent-related events in callbacks:
+
+```go
+const ComponentOfAgent components.Component = "Agent"
+```
+
+Used in `callbacks.RunInfo.Component` to filter callback events related only to Agents.
+
+### AgentCallbackInput
+
+Input type for Agent callbacks, passed in the `OnStart` callback:
+
+```go
+type AgentCallbackInput struct {
+ // Input contains the Agent input for new runs. Nil when resuming execution.
+ Input *AgentInput
+ // ResumeInfo contains information when resuming from an interrupt. Nil for new runs.
+ ResumeInfo *ResumeInfo
+}
+```
+
+
| Decision Autonomy | Description |
-| Autonomous Decision | Within the Agent, based on its available downstream Agents, autonomously select a downstream Agent for assistance when needed. Generally, the Agent makes decisions based on LLM internally, but even if selection is based on preset logic, it is still considered autonomous decision from the Agent's external perspective |
-| Preset Decision | Pre-set the next Agent after an Agent executes a task. The execution order of Agents is predetermined and predictable |
+| Autonomous Decision | Within the Agent, based on its available downstream Agents, when assistance is needed, it autonomously selects a downstream Agent for assistance. Generally, the Agent makes decisions based on LLM internally, but even if selection is based on preset logic, it is still considered autonomous decision from the Agent's external perspective |
+| Preset Decision | The next Agent after an Agent executes a task is predetermined. The execution order of Agents is predetermined and predictable |
### Composition Primitives
-| Type | Description | Running Mode | Collaboration Mode | Context Strategy | Decision Autonomy |
-| SubAgents | Combine the user-provided agent as the parent Agent and the user-provided subAgents list as child Agents to form an Agent capable of autonomous decision-making, where Name and Description serve as the Agent's name identifier and description.Currently limited to one Agent having only one parent AgentUse the SetSubAgents function to build a "multi-branch tree" form of Multi-AgentIn this "multi-branch tree", AgentName must remain unique |  | Transfer | Upstream Agent Full Dialogue | Autonomous Decision |
-| Sequential | Combine the user-provided SubAgents list into a Sequential Agent that executes in order, where Name and Description serve as the Sequential Agent's name identifier and description. When the Sequential Agent executes, it runs the SubAgents list in order until all Agents have been executed once. |  | Transfer | Upstream Agent Full Dialogue | Preset Decision |
-| Parallel | Combine the user-provided SubAgents list into a Parallel Agent that executes concurrently based on the same context, where Name and Description serve as the Parallel Agent's name identifier and description. When the Parallel Agent executes, it runs the SubAgents list concurrently and finishes after all Agents complete execution. |  | Transfer | Upstream Agent Full Dialogue | Preset Decision |
-| Loop | Execute the user-provided SubAgents list in array order, cycling repeatedly, to form a Loop Agent, where Name and Description serve as the Loop Agent's name identifier and description. When the Loop Agent executes, it runs the SubAgents list in order and finishes after all Agents complete execution. |  | Transfer | Upstream Agent Full Dialogue | Preset Decision |
-| AgentAsTool | Convert an Agent into a Tool to be used as a regular Tool by other Agents. Whether an Agent can call other Agents as Tools depends on its own implementation. The ChatModelAgent provided in adk supports the AgentAsTool functionality |  | ToolCall | New Task Description | Autonomous Decision |
+| Type | Description | Running Mode | Collaboration Method | Context Strategy | Decision Autonomy |
+| SubAgents | Combines the user-provided agent as the parent Agent and the user-provided subAgents list as child Agents to form an Agent capable of autonomous decision-making. The Name and Description serve as the Agent's name identifier and description.Currently, an Agent can only have one parent AgentUse the SetSubAgents function to build a "multi-tree" form of Multi-AgentWithin this "multi-tree", AgentName must remain unique |  | Transfer | Upstream Agent Full Conversation | Autonomous Decision |
+| Sequential | Combines the user-provided SubAgents list into a Sequential Agent that executes in order. The Name and Description serve as the Sequential Agent's name identifier and description. When the Sequential Agent executes, it runs the SubAgents list in order until all Agents have been executed. |  | Transfer | Upstream Agent Full Conversation | Preset Decision |
+| Parallel | Combines the user-provided SubAgents list into a Parallel Agent that executes concurrently based on the same context. The Name and Description serve as the Parallel Agent's name identifier and description. When the Parallel Agent executes, it runs the SubAgents list concurrently and finishes when all Agents have completed. |  | Transfer | Upstream Agent Full Conversation | Preset Decision |
+| Loop | Executes the user-provided SubAgents list in array order sequentially and repeatedly, forming a Loop Agent. The Name and Description serve as the Loop Agent's name identifier and description. When the Loop Agent executes, it runs the SubAgents list in order and finishes when all Agents have completed. |  | Transfer | Upstream Agent Full Conversation | Preset Decision |
+| AgentAsTool | Converts an Agent into a Tool to be used by other Agents as a regular Tool. Whether an Agent can call other Agents as Tools depends on its own implementation. The ChatModelAgent provided in ADK supports the AgentAsTool functionality |  | ToolCall | Fresh Task Description | Autonomous Decision |
## Context Passing
-When building multi-Agent systems, efficient and accurate information sharing between different Agents is crucial. Eino ADK provides two core context passing mechanisms to meet different collaboration needs: History and SessionValues.
+When building multi-Agent systems, efficient and accurate sharing of information between different Agents is crucial. Eino ADK provides two core context passing mechanisms to meet different collaboration needs: History and SessionValues.
### History
#### Concept
-History corresponds to the [Upstream Agent Full Dialogue Context Strategy]. Every AgentEvent produced by each Agent in a multi-Agent system is saved to History. When calling a new Agent (Workflow/Transfer), the AgentEvents in History are converted and appended to the AgentInput.
+History corresponds to the [Upstream Agent Full Conversation context strategy]. Every AgentEvent produced by each Agent in a multi-Agent system is saved to History. When calling a new Agent (Workflow/Transfer), the AgentEvents in History are converted and concatenated into the AgentInput.
-By default, Assistant or Tool Messages from other Agents are converted to User Messages. This is equivalent to telling the current LLM: "Just now, Agent_A called some_tool and returned some_result. Now it's your turn to decide."
+By default, Assistant or Tool Messages from other Agents are converted to User Messages. This is equivalent to telling the current LLM: "Just now, Agent_A called some_tool and returned some_result. Now it's your turn to make a decision."
Through this approach, other Agents' behaviors are treated as "external information" or "factual statements" provided to the current Agent, rather than its own behaviors, thus avoiding LLM context confusion.