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

Eino Notes — Agentic Advanced

Eino Notes — Agentic Advanced Features

Mon Jul 20 2026
1302 words · 10 minutes

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

Eino Agentic Advanced Features Guide

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

📖 Series docs: Intro Notes | Agentic Advanced | Appendix I Flow | Appendix II Components | Appendix III Tools

📁 Code sources:

  • adk/agentic/research_assistant/ — AgenticModel + AgenticMessage full-featured research assistant
  • adk/agentic/retry_max_output_tokens/ — auto-retry on output truncation

📖 Prerequisites: This guide assumes you have already mastered the content of sections one through five in the Intro Notes.


Quick Look: ChatModel → AgenticModel Type Mapping Table (Read This First!)

Before diving into details, memorize the core mapping — Agentic means adding Typed to every type name, and replacing every Message with AgenticMessage:

Standard version (Chat Completions)Agentic version (Responses API)
openai.NewChatModel()agenticopenai.New() / agenticark.New()
schema.UserMessage("...")schema.UserAgenticMessage("...")
adk.NewChatModelAgent(...)adk.NewTypedChatModelAgent[*schema.AgenticMessage](...)
adk.NewRunner(...)adk.NewTypedRunner[*schema.AgenticMessage](...)
event.Output.MessageOutput.GetMessage()adk.TypedGetMessage(event)
adk.ChatModelAgentMiddlewareadk.TypedChatModelAgentMiddleware[*schema.AgenticMessage]
adk.ModelRetryConfigadk.TypedModelRetryConfig[*schema.AgenticMessage]
filesystem.New(...)filesystem.NewTyped[*schema.AgenticMessage](...)

🧠 One-line summary: The Agentic series is Eino’s complete wrapper for the Responses API — replace ChatModel with AgenticModel, replace Message with AgenticMessage, and the usage of other concepts (Agent / Runner / Middleware / Tool) stays the same, but all use the Typed generic variants.


Background: Chat Completions API → Responses API

In the Intro Notes, all code is based on the Chat Completions API (ChatModel + *schema.Message). This is the API form introduced by OpenAI in 2022.

But starting in 2025, mainstream model vendors began pushing the Responses API, which is a new generation of API form. The core differences between the two:

DimensionChat Completions APIResponses API
Message type*schema.Message (single role + content)*schema.AgenticMessage (contains ContentBlocks)
Model interfaceeinoModel.ChatModeleinoModel.AgenticModel
Agent typeadk.NewChatModelAgentadk.NewTypedChatModelAgent[*schema.AgenticMessage]
Runner typeadk.NewRunneradk.NewTypedRunner[*schema.AgenticMessage]
Single call returnOne text replyMultiple ordered structured events (reasoning → tool call → reasoning again → text)
Server-side tools❌ All executed on the client✅ Executed directly on the vendor side (e.g. web_search)
Output truncation detectionNo standard signalstatus=incomplete + reason=max_output_tokens

I. Core Concept: AgenticMessage and ContentBlock

1.1 Why do we need AgenticMessage?

In the Chat Completions API, a single model call returns only one message. The Responses API returns all structured intermediate steps in a single call. To carry this “multi-event” return, the original *schema.Message (with only one Role + Content string) is no longer enough:

ChatModel returns: ┌──────────────────────────┐
│ Role: Assistant │
│ Content: "The answer is 42" │
└──────────────────────────┘
Only a single flat message
AgenticModel ┌──────────────────────────────────────────────┐
returns: │ Role: Assistant │
│ ContentBlocks: [ │
│ [0] type: reasoning │
│ text: "I need to search first..." │
│ [1] type: server_tool_call │
│ name: web_search │
│ [2] type: reasoning │
│ text: "Search results show..." │
│ [3] type: text │
│ text: "Based on the analysis, the answer is 42" │
│ ] │
└──────────────────────────────────────────────┘
One message containing multiple ordered structured Blocks

1.2 ContentBlock Type Quick Reference

Each Block has a Type field indicating its category:

Block typeMeaningProduced byAppears in which message
reasoningThe model’s reasoning / thinking processModelAssistant message
textNormal text outputModelAssistant message
server_tool_callCalling a server-side tool (e.g. web_search)ModelAssistant message
function_tool_callCalling a client-side local toolModelAssistant message
function_tool_resultLocal tool execution resultFrameworkUser message (fed back to model)
thinkingDeep thinking contentModelAssistant message

🧠 Key understanding: server_tool_call executes on the model vendor’s servers (your code can’t see the execution process), while function_tool_call is returned to your Agent for local execution.


II. AgenticModel: Connecting to the Responses API

2.1 Two Vendor Integrations

import (
einoModel "github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino-ext/components/model/agenticark" // Volcengine ARK
"github.com/cloudwego/eino-ext/components/model/agenticopenai" // OpenAI
)
// ARK
model, err := agenticark.New(ctx, &agenticark.Config{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL_ID"),
BaseURL: os.Getenv("ARK_BASE_URL"),
})
// OpenAI
model, err := agenticopenai.New(ctx, &agenticopenai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
Model: os.Getenv("OPENAI_MODEL_ID"),
BaseURL: os.Getenv("OPENAI_BASE_URL"),
})

You don’t need to write a search Tool yourself — just declare it, and the search is done on the vendor side:

// ARK server-side web_search
agenticark.WithServerTools([]*agenticark.ServerToolConfig{
{WebSearch: &arkResponses.ToolWebSearch{
Type: arkResponses.ToolType_web_search,
Limit: ptrOf[int64](6),
}},
})
// OpenAI server-side web_search
agenticopenai.WithServerTools([]*agenticopenai.ServerToolConfig{
{WebSearch: &openaiResponses.WebSearchToolParam{
Type: openaiResponses.WebSearchToolTypeWebSearch,
}},
})

💡 Server-side vs local tools: The call and execution of a server-side tool both happen on the vendor side. Your code will only see a server_tool_call block in content_blocks, and there will be no corresponding function_tool_result — the result is consumed internally by the model.


III. Typed Generic System

3.1 Complete Assembly Example (compared with Intro Notes §3)

The Agent assembly steps are exactly the same as Section III of the Intro Notes, only the types are switched to Typed + AgenticMessage:

func newResearchAssistant(ctx context.Context) (adk.TypedAgent[*schema.AgenticMessage], error) {
// ─── Step 1: Create the AgenticModel ───
agenticModel, _ := newAgenticModel(ctx)
// ─── Step 2: Create local tools (same as before, InferTool) ───
tools, _ := buildTools()
// ─── Step 3: Create the TypedChatModelAgent ───
agent, err := adk.NewTypedChatModelAgent[*schema.AgenticMessage](ctx,
&adk.TypedChatModelAgentConfig[*schema.AgenticMessage]{
Name: "AgenticResearchAssistant",
Instruction: "...",
Model: agenticModel, // ← AgenticModel
ToolsConfig: adk.ToolsConfig{ // ← Same as the ChatModel version!
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: tools,
},
},
MaxIterations: 8,
})
return agent, err
}

The rest of the logic (Middleware, etc.) is exactly the same as the ChatModel version, just add the Typed prefix

3.2 Using TypedRunner

// Create (same pattern as before)
runner := adk.NewTypedRunner[*schema.AgenticMessage](adk.TypedRunnerConfig[*schema.AgenticMessage]{
Agent: agent,
EnableStreaming: true,
})
// Build input (use UserAgenticMessage instead of UserMessage)
input := schema.UserAgenticMessage("Please write a research report...")
// Run
iter := runner.Run(ctx, []*schema.AgenticMessage{input})
// Consume events — ⚠️ Key change: use TypedGetMessage
for {
event, ok := iter.Next()
if !ok { break }
msg, _, err := adk.TypedGetMessage(event) // ← Not GetMessage()
if msg != nil {
fmt.Print(msg.String())
}
}

🧠 Comparison with Intro Notes:

// Old: ChatModel path
msg, err := event.Output.MessageOutput.GetMessage()
// New: AgenticModel path
msg, typedagentevent, err := adk.TypedGetMessage(event)

IV. ModelRetryConfig: Auto-retry on Output Truncation

4.1 Problem Scenario

The Responses API has a max_output_tokens limit per call. When set too small, the output gets truncated:

status=incomplete
incomplete_details.reason=max_output_tokens

4.2 Solution

Eino ADK provides TypedModelRetryConfig, which automatically discards incomplete output, increases the budget, and re-invokes:

var retryMaxTokens = []int{4096, 8192, 16384}
ModelRetryConfig: &adk.TypedModelRetryConfig[*schema.AgenticMessage]{
MaxRetries: len(retryMaxTokens),
ShouldRetry: func(ctx context.Context, retryCtx *adk.TypedRetryContext[*schema.AgenticMessage]) *adk.TypedRetryDecision[*schema.AgenticMessage] {
if !isMaxOutputTokensIncomplete(retryCtx.OutputMessage) {
return nil // No retry needed
}
nextMaxTokens := retryMaxTokens[retryCtx.RetryAttempt-1]
return &adk.TypedRetryDecision[*schema.AgenticMessage]{
Retry: true,
AdditionalOptions: []einoModel.Option{einoModel.WithMaxTokens(nextMaxTokens)},
Backoff: 100 * time.Millisecond,
}
},
},

4.3 Comparison with the ModelRetryConfig in Intro Notes §4.4

DimensionChatModel versionAgentic version
Typeadk.ModelRetryConfigadk.TypedModelRetryConfig[*schema.AgenticMessage]
Detection basiserr (HTTP error)OutputMessage.ResponseMeta (response status)
Retry methodRetry as-isRetry after increasing max_output_tokens
Use caseRate limiting, network errorsOutput truncation

V. Agentic Panorama Quick Reference

5.1 New Capabilities

CapabilityChatModel have it?How AgenticModel achieves it
Reasoning process visiblereasoning ContentBlock
Server-side toolsserver_tool_call + WithServerTools
Output truncation detectionIndirect (finish_reason)Direct: status=incomplete
Multiple events in one call❌ Multiple round-trips✅ Single call returns multiple ContentBlocks
Deep thinkingthinking ContentBlock + WithReasoning

5.2 Common Import Paths

// AgenticModel implementations
"github.com/cloudwego/eino-ext/components/model/agenticopenai" // OpenAI Responses API
"github.com/cloudwego/eino-ext/components/model/agenticark" // Volcengine ARK
// Core interfaces
"github.com/cloudwego/eino/components/model" // AgenticModel interface
"github.com/cloudwego/eino/schema" // AgenticMessage, ContentBlock
"github.com/cloudwego/eino/adk" // TypedChatModelAgent, TypedRunner, TypedModelRetryConfig
// Middleware (Typed version)
"github.com/cloudwego/eino/adk/middlewares/filesystem" // filesystem.NewTyped
// Backend
"github.com/cloudwego/eino-ext/adk/backend/local" // localbackend.NewBackend

5.3 Summary: When Should You Use Agentic?

Your scenario → What to use
─────────────────────────────────────────────────────────
Simple Q&A, one-shot call → ChatModel (Intro Notes ./note §2)
Multi-turn dialogue + local tools + interrupt recovery → ChatModelAgent (Intro Notes ./note §3)
Need server-side search (web_search) → AgenticModel ✅
Need to see the model's reasoning process → AgenticModel ✅
Need auto-retry on output truncation → AgenticModel ✅
Need to mix reasoning + tools + text in one call → AgenticModel ✅
Existing ChatModelAgent code runs fine → Don't migrate, unless you need the above

🧠 Progressive adoption: Agentic is not meant to replace ChatModel — they are two parallel systems. When you need advanced features, the migration path is also clear: change the types from ChatModel/Message to AgenticModel/AgenticMessage, and the rest of the logic is largely unchanged.

📇 Quick Reference Card for This Article

See Typed → use AgenticMessage:
NewChatModelAgent → NewTypedChatModelAgent[*AgenticMessage]
NewRunner → NewTypedRunner[*AgenticMessage]
GetMessage() → TypedGetMessage()
ModelRetryConfig → TypedModelRetryConfig
filesystem.New → filesystem.NewTyped[*AgenticMessage]
Six ContentBlock types: reasoning / text / server_tool_call
function_tool_call / function_tool_result / thinking
New capabilities:
reasoning Block (visible reasoning), server_tool_call (server-side tools)
web_search (no need to write your own search Tool), output truncation detection + retry

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

📖 Keep reading:


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

Eino Notes — Agentic Advanced

Mon Jul 20 2026
1302 words · 10 minutes
Cover
Sample track
Sample artist
Cover
Sample track
Sample artist
0:00 / 0:00