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

Eino Study Notes — Appendix 2

Eino Study Notes — Appendix 2: The Components Module

Mon Jul 20 2026
1899 words · 13 minutes

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

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

Appendix 2: The Components Module

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

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

📁 Code root: components/

📖 Prerequisites: This appendix focuses on Eino’s replaceable components — Model, Retriever, and Document Parser. These components implement a unified interface and can be freely plugged in/out within Graph/Agent/Runner.


At a Glance: Component System Overview

Eino’s component layer is the building block for all higher-level abstractions (ADK, Flow, Compose). Each component type defines a standard interface, and different implementations can be swapped seamlessly:

┌─────────────────────────────────────────┐
│ ADK / Flow / Compose │ ← Upper-layer orchestration
├─────────────────────────────────────────┤
│ ChatModel │ Retriever │ Parser │ ... │ ← Component interfaces
├─────────────────────────────────────────┤
│ OpenAI │ ARK │ VikingDB │ PDF │ HTML │ ← Concrete implementations
└─────────────────────────────────────────┘

📇 Quick-Reference Card for This Appendix

Three major component categories:
Model: A/B routing (ABRouter) / HTTP logging (CurlRT)
Retriever: Multi-Query (rewrite query) / Router (multi-data-source routing)
Parser: TextParser → ExtParser → CustomParser (dispatch by extension)
Core pattern: All implement a unified interface → freely pluggable in any orchestration

1. Model: A/B Test Routing

📁 components/model/abtest/

1.1 What problem does this solve?

Those with a data-analysis background will surely be familiar with this.

Suppose you wrote an Agent using GPT-4o. Now the team has onboarded DeepSeek, which is 80% cheaper but possibly slightly lower quality. Would you dare to fully switch over to it right away?

No. What you’d prefer is:

100% of user requests
├─ 90% → GPT-4o (main, stable)
└─ 10% → DeepSeek (experiment, observe)

Run it for a week and compare user satisfaction, latency, and cost between the two groups — let the data decide whether to switch. That is A/B testing.

ABRouterChatModel is exactly this “traffic distributor” — externally identical to a normal ChatModel, but internally it routes requests to different models according to the rules you write.

1.2 Full Example

import "github.com/cloudwego/eino-examples/components/model/abtest"
// Prepare two candidate models
gpt4o, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{...})
deepseek, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: "https://api.deepseek.com/v1", // Point to DeepSeek
Model: "deepseek-chat",
})
// Router: A/B split based on the last digit of userID
router := abtest.NewABRouterChatModel(func(
ctx context.Context, in []*schema.Message, _ ...model.Option,
) (string, model.BaseChatModel, error) {
// Extract userID from context (in a real project, from the request context)
userID, _ := ctx.Value("userID").(string)
if userID != "" && userID[len(userID)-1] < '5' { // 50% of traffic
return "deepseek", deepseek, nil // Experiment group
}
return "gpt4o", gpt4o, nil // Control group
})
// Used exactly the same as a normal ChatModel!
msg, _ := router.Generate(ctx, messages)

1.3 More Than A/B Testing — Three Real Use Cases

ScenarioRouting rulePurpose
A/B testinguserID hash → 10% new model, 90% old modelCompare results before deciding on full switch
Cost optimizationMessage length < 100 → cheap model; ≥ 100 → strong modelSimple questions don’t need a sledgehammer
Canary release1% traffic → new model; observe one day with no anomalies → 5% → 20% → 100%Gradually ramp up; quick rollback if issues arise

🧠 Key understanding: The key value of ABRouter is that the external interface stays unchanged — your Agent code doesn’t need to know how many models are running underneath, and changing routing rules requires no changes to business code.


2. Model: HTTP Transport Logging (cURL-style Debugging)

📁 components/model/httptransport/

2.1 What problem does this solve?

You wrote an Agent, and after running it the LLM returned a strange result. You want to confirm:

  • What did the request actually sent to the API look like? (Was the prompt truncated? Are the parameters correct?)
  • What was the raw response returned by the API? (Did JSON parsing go wrong?)
  • Can you copy the request as a cURL command and replay it in the terminal?

httptransport does exactly this — it intercepts all HTTP requests/responses and prints logs in a copy-pasteable cURL command format, while automatically redacting the API Key.

2.2 Core Mechanism

client := &http.Client{
Transport: httptransport.NewCurlRT(
http.DefaultTransport, // Base Transport (the one that actually sends requests)
httptransport.WithLogger(log.Default()),
httptransport.WithPrintAuth(false), // ⚠️ API Key redacted (not printed by default)
httptransport.WithMaskHeaders([]string{"X-API-KEY"}),
httptransport.WithStreamLogging(true), // Log streaming responses too
httptransport.WithMaxStreamLogBytes(8192),
),
}
// Inject into ChatModel — logs are printed automatically on subsequent calls
chatModel, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
HTTPClient: client,
})

2.3 Log Output Effect

Request log — copy directly to the terminal to replay:

[curl request] curl -X POST 'https://api.openai.com/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: <redacted>' \
--data '{"model":"gpt-4o","messages":[{"role":"user","content":"hello"}]}'

Response log — see the raw JSON returned by the API:

[curl response] HTTP/1.1 200
{"id":"chatcmpl-xxx","choices":[{"message":{"role":"assistant","content":"Hello! How can I help?"}}]}

2.4 Configuration Quick Reference

OptionEffectDefault
WithPrintAuth(false)Don’t print Authorization HeaderRedacted
WithMaskHeaders(...)Extra headers to redact
WithStreamLogging(true)Log streaming responses chunk by chunkNot logged
WithMaxStreamLogBytes(n)Max bytes for streaming log8192
WithCtxLogger(...)Extract request ID from ctx and inject into log

🧠 Key understanding: This is a debugging/troubleshooting tool, not a production necessity. Recommended to keep it on during development, and turn it off in production or only enable it on sampled traffic.


3. Retriever: Multi-Query Retrieval

📁 components/retriever/multiquery/

3.1 What problem does this solve?

In RAG (Retrieval-Augmented Generation) scenarios, the user’s query is often not precise enough:

User input: "How to improve code quality"
The actual titles of relevant docs in the vector store are:
"Unit Testing Best Practices"
"Code Review Process Guidelines"
"Static Analysis Tool Configuration Guide"

Searching directly with “How to improve code quality” in the vector store may return nothing — there is a semantic gap between the user’s colloquial phrasing and the document’s technical terminology.

Multi-Query’s approach: First let the LLM translate the user’s words into 3 queries from different angles, search each one, then merge and deduplicate the results.

3.2 Workflow

User input: "tourist attraction" (too vague)
↓ LLM auto-rewrites
Variant 1: "best tourist attractions for families" ← from the "family" angle
Variant 2: "outdoor sightseeing spots recommendations" ← from the "outdoor" angle
Variant 3: "popular travel destinations and landmarks" ← from the "famous attractions" angle
↓ Retrieve from the same vector store separately
Result 1: [doc_a, doc_b]
Result 2: [doc_b, doc_d] → FusionFunc merges and dedupes → [doc_a, doc_b, doc_c, doc_d, doc_e, doc_f]
Result 3: [doc_c, doc_e, doc_f]

3.3 Full Implementation

import "github.com/cloudwego/eino/flow/retriever/multiquery"
mqr, _ := multiquery.NewRetriever(ctx, &multiquery.Config{
RewriteHandler: func(ctx context.Context, query string) ([]string, error) {
out, _ := llm.Generate(ctx, []*schema.Message{
schema.SystemMessage("Generate 3 rewritten search queries, one per line."),
schema.UserMessage(query),
})
return strings.Split(out.Content, "\n"), nil
},
MaxQueriesNum: 3, // Generate at most 3 variants
OrigRetriever: vk, // Underlying vector retrieval engine
})
docs, _ := mqr.Retrieve(ctx, "tourist attraction")
// docs contains the merged results from all variants

🧠 Key understanding: Multi-Query is “using an LLM for query augmentation” — use a cheap, fast model to do the rewriting, then take the rewritten results to search the vector store. FusionFunc dedupes by document ID by default, preserving the order of first appearance.


4. Retriever: Router Retrieval

📁 components/retriever/router/

4.1 What problem does this solve?

Multi-Query solves the problem of “searching one knowledge base inaccurately.” Router solves the problem of “multiple knowledge bases, not knowing which one to search.”

Your system has three vector stores:
product_vdb → stores product info (name, price, specs)
review_vdb → stores user reviews (ratings, review text)
faq_vdb → stores FAQs (Q&A pairs)
User asks "How much does this product cost?" → search product_vdb
User asks "Is this product good?" → search review_vdb
User asks "How to return it?" → search faq_vdb

Router Retriever lets you declare “which stores exist + which queries go to which store,” and a single call automatically routes and merges.

4.2 Full Implementation

rr, _ := router.NewRetriever(ctx, &router.Config{
Retrievers: map[string]retriever.Retriever{
"product": productRetriever, // Product vector store
"review": reviewRetriever, // Review vector store
"faq": faqRetriever, // FAQ vector store
},
Router: func(ctx context.Context, query string) ([]string, error) {
var targets []string
// Route to different engines by keyword
if strings.Contains(query, "price") || strings.Contains(query, "specs") {
targets = append(targets, "product")
}
if strings.Contains(query, "review") || strings.Contains(query, "good") {
targets = append(targets, "review")
}
if strings.Contains(query, "return") || strings.Contains(query, "how") {
targets = append(targets, "faq")
}
if len(targets) == 0 {
targets = []string{"product", "review", "faq"} // Fallback: search all
}
return targets, nil
},
FusionFunc: nil, // nil = default RRF (Reciprocal Rank Fusion) fusion
})

4.3 Difference from Multi-Query

DimensionMulti-QueryRouter
What it solvesUser’s phrasing too vague, vector store search inaccurateMultiple vector stores, not knowing which to search
Retrieval countN variants × 1 retriever = N timesM selected retrievers × 1 time = M times
Core ideaUse LLM to rewrite query, search from multiple anglesAnalyze query content, dispatch to corresponding stores
Use caseSingle knowledge base, improve recallMultiple knowledge bases, joint retrieval

💡 The two can be combined — first Router to multiple retrievers, then Multi-Query rewrites for each retriever, then global fusion.


5. Document Parser

5.1 Text Parser (Simplest Entry Point)

import "github.com/cloudwego/eino/components/document/parser"
textParser := parser.TextParser{}
docs, _ := textParser.Parse(ctx, strings.NewReader("hello world"))

5.2 ExtParser: Auto-Select Parser by File Extension

Motivation: The file you upload might be HTML, PDF, Markdown, or plain text. Writing a bunch of if/switch is too tedious. ExtParser automatically dispatches to the corresponding Parser based on the file suffix:

extParser, _ := parser.NewExtParser(ctx, &parser.ExtParserConfig{
Parsers: map[string]parser.Parser{
".html": htmlParser, // → Use CSS Selector to extract body content
".pdf": pdfParser, // → Use PDF parsing engine to extract text
},
FallbackParser: textParser, // Unknown format → read as plain text
})
file, _ := os.Open("./testdata/test.html")
docs, _ := extParser.Parse(ctx, file, parser.WithURI(filePath))
// ↑ WithURI must be passed — ExtParser determines the format by the file path suffix

5.3 Custom Parser (Four-Step Implementation)

When the built-in TextParser / HTML / PDF don’t meet your needs (e.g., parsing Word documents, Excel tables, or custom formats):

  1. Define the custom options struct (type options struct{...})
  2. Create the Option function with parser.WrapImplSpecificOptFn
  3. Define the Config + struct + constructor
  4. Implement Parse(ctx, io.Reader, ...parser.Option) ([]*schema.Document, error)

Core techniques:

  • parser.GetCommonOptions() extracts framework-common options (WithURI, WithExtraMeta)
  • parser.GetImplSpecificOptions() extracts custom options
  • parser.WrapImplSpecificOptFn wraps a custom func(*options) into a parser.Option

6. Panoramic Quick Reference

Component typeWhat it solvesSectionCore API
ABRouterDare not fully switch to new model → split traffic by rules for A/B comparison1NewABRouterChatModel(routerFn)
CurlRTAPI crashed and you don’t know what was sent → cURL-style log debugging2NewCurlRT(transport, opts...)
Multi-QueryUser searches too vaguely → LLM rewrites N variants and searches each3multiquery.NewRetriever({RewriteHandler, OrigRetriever})
Router RetrieverMultiple vector stores, don’t know which to search → auto-route by query content4router.NewRetriever({Retrievers, Router})
ExtParserMultiple document formats → auto-dispatch by extension5NewExtParser({Parsers: map[".ext"]parser})

Common import paths

// A/B test routing
"github.com/cloudwego/eino-examples/components/model/abtest"
// HTTP transport logging
"github.com/cloudwego/eino-examples/components/model/httptransport"
// Retriever augmentation
"github.com/cloudwego/eino/flow/retriever/multiquery" // Multi-Query
"github.com/cloudwego/eino/flow/retriever/router" // Router
// Document parser
"github.com/cloudwego/eino/components/document/parser" // Parser interface, TextParser, ExtParser
"github.com/cloudwego/eino-ext/components/document/parser/html" // HTML
"github.com/cloudwego/eino-ext/components/document/parser/pdf" // PDF

Layered Architecture Review

┌──────────────────────────────────────────────────┐
│ ADK layer (Intro Notes §1~§7) │
│ ChatModelAgent, Runner, Middleware, Interrupt │
├──────────────────────────────────────────────────┤
│ Flow layer (Appendix 1: Flow Module) │
│ react.NewAgent, host.NewMultiAgent │
├──────────────────────────────────────────────────┤
│ Compose layer (Intro Notes §6.4) │
│ Graph, Chain, Workflow, Branch, ProcessState │
├──────────────────────────────────────────────────┤
│ Component layer (this appendix) │
│ ChatModel, Retriever, Parser, Tool │
└──────────────────────────────────────────────────┘

📇 Quick-Reference Card for This Appendix

Model:
ABRouter = traffic distributor (A/B testing / cost optimization / canary release)
CurlRT = HTTP debugger (cURL-style logs + automatic API Key redaction)
Retriever:
MultiQuery = use LLM to rewrite query → search same store from multiple angles → merge and dedupe
Router = analyze query content → dispatch to different stores → fuse results
Parser:
TextParser → ExtParser (map by extension) → CustomParser (WrapImplSpecificOptFn)

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

📖 Keep reading:


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

Eino Study Notes — Appendix 2

Mon Jul 20 2026
1899 words · 13 minutes
Cover
Sample track
Sample artist
Cover
Sample track
Sample artist
0:00 / 0:00