Build a Go AI Agent in 3 Days: Hands‑On RAG + ReAct Implementation

This tutorial walks Go developers through creating a fully functional AI Agent using Eino, covering knowledge‑base indexing with VikingDB, defining retrieval, weather, and web‑search tools, assembling a ReAct graph, running interactive queries, and adapting the demo for production environments.

AI Illustrated Series
AI Illustrated Series
AI Illustrated Series
Build a Go AI Agent in 3 Days: Hands‑On RAG + ReAct Implementation

Overall Architecture

The agent consists of two phases: (1) a one‑time knowledge‑base indexing step that loads documents, splits them, embeds them, and stores the vectors; (2) a runtime loop where the model decides whether to query the knowledge base, call a tool, or answer directly, repeating until a response is produced.

Step 1: Knowledge‑Base Indexing

Load markdown files, split them into documents, embed the text with text-embedding-3-small via OpenAI, and store the vectors in VikingDB (or Qdrant for local testing). The following environment variables are required for VikingDB:

export VOLC_ACCESSKEY="your-access-key"
export VOLC_SECRETKEY="your-secret-key"
export VIKINGDB_REGION="cn-beijing"
export VIKINGDB_HOST="vikingdb.volces.com"

If only local testing is needed, Qdrant can be used without credentials.

Implementation of the indexing function:

package main

import (
    "context"
    "log"
    "os"
    "github.com/cloudwego/eino/components/document"
    "github.com/cloudwego/eino/components/indexer"
    "github.com/cloudwego/eino/components/retriever"
    "github.com/cloudwego/eino-ext/components/document/loader/file"
    "github.com/cloudwego/eino-ext/components/embedding/openai"
    "github.com/cloudwego/eino-ext/components/indexer/volc_vikingdb"
)

func buildKnowledgeBase(ctx context.Context, filePaths []string) (retriever.Retriever, error) {
    // 1. Load Markdown documents
    fileLoader, _ := file.NewFileLoader(ctx, &file.FileLoaderConfig{})
    var allDocs []*document.Document
    for _, path := range filePaths {
        docs, _ := fileLoader.Load(ctx, document.Source{URI: path})
        allDocs = append(allDocs, docs...)
    }
    log.Printf("Loaded %d document chunks", len(allDocs))

    // 2. Create embeddings
    embedder, _ := openai.NewEmbedder(ctx, &openai.EmbeddingConfig{
        Model:  "text-embedding-3-small",
        APIKey: os.Getenv("OPENAI_API_KEY"),
    })

    // 3. Store vectors
    idx, _ := volc_vikingdb.NewIndexer(ctx, &volc_vikingdb.IndexerConfig{Collection: "eino_knowledge"})
    _, err := idx.Store(ctx, &indexer.StoreRequest{Documents: allDocs, Embedding: embedder})
    if err != nil {
        return nil, err
    }
    log.Println("Knowledge base indexing completed")
    return idx.GetRetriever(), nil
}

Step 2: Defining Tools

The agent needs three tools: knowledge retrieval, weather lookup, and web search. Each tool implements Info (metadata) and InvokableRun (execution).

type KnowledgeTool struct { retriever retriever.Retriever }

func (t *KnowledgeTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    return &schema.ToolInfo{
        Name: "get_knowledge",
        Desc: "Retrieve Go/Eino docs from internal knowledge base",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "query": {Type: "string", Desc: "Search keyword or question", Required: true},
        }),
    }, nil
}

func (t *KnowledgeTool) InvokableRun(ctx context.Context, args string, opts ...tool.Option) (string, error) {
    var params struct{ Query string }
    json.Unmarshal([]byte(args), ¶ms)
    docs, err := t.retriever.Retrieve(ctx, params.Query)
    if err != nil {
        return "", err
    }
    var result strings.Builder
    for i, doc := range docs {
        result.WriteString(fmt.Sprintf("[%d] %s
", i+1, doc.Content))
    }
    return result.String(), nil
}

Weather tool returns static weather text; web‑search tool returns a placeholder string.

type WeatherTool struct{}
func (t *WeatherTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    return &schema.ToolInfo{
        Name: "get_weather",
        Desc: "Query real‑time weather for a city",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "city": {Type: "string", Desc: "City name, e.g. 北京", Required: true},
        }),
    }, nil
}
func (t *WeatherTool) InvokableRun(ctx context.Context, args string, opts ...tool.Option) (string, error) {
    var params struct{ City string }
    json.Unmarshal([]byte(args), ¶ms)
    return fmt.Sprintf("%s 今天晴转多云,22-30°C,适宜出行", params.City), nil
}
type SearchTool struct{}
func (t *SearchTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    return &schema.ToolInfo{
        Name: "web_search",
        Desc: "Search the internet for latest information",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "query": {Type: "string", Desc: "Search keyword", Required: true},
        }),
    }, nil
}
func (t *SearchTool) InvokableRun(ctx context.Context, args string, opts ...tool.Option) (string, error) {
    var params struct{ Query string }
    json.Unmarshal([]byte(args), ¶ms)
    return fmt.Sprintf("关于「%s」搜索结果:找到了3条相关信息...", params.Query), nil
}

Step 3: Assembling the Agent Graph

The graph combines a ChatModel, the three tools, and the knowledge retriever. The system prompt tells the model to prioritize get_knowledge, then get_weather, and finally web_search when needed.

func buildAgent(ctx context.Context, kbRetriever retriever.Retriever) (*compose.GraphRunner, error) {
    chatModel, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{Model: "gpt-4o-mini", APIKey: os.Getenv("OPENAI_API_KEY")})
    agent, err := react.NewAgent(ctx, &react.AgentConfig{Model: chatModel,
        ToolsConfig: compose.ToolsNodeConfig{Tools: []tool.InvokableTool{&KnowledgeTool{retriever: kbRetriever}, &WeatherTool{}, &SearchTool{}}},
        SystemPrompt: `你是一个Go语言AI助手。
1. 技术问题优先用 get_knowledge 工具查内部知识库
2. 天气查询用 get_weather 工具
3. 知识库查不到时再用 web_search 搜索互联网
4. 回答简洁直接,像 Go 社区开发者一样的风格
5. 超出能力范围的问题,诚实告知`,
    })
    if err != nil { return nil, err }
    runner, err := agent.Compile(ctx)
    if err != nil { return nil, err }
    log.Println("Agent 编译完成,可以开始对话")
    return runner, nil
}

Step 4: Running a Conversation

func main() {
    ctx := context.Background()
    kbRetriever, err := buildKnowledgeBase(ctx, []string{"./docs/eino_guide.md", "./docs/go_tips.md"})
    if err != nil { log.Fatal("知识库构建失败:", err) }
    agent, err := buildAgent(ctx, kbRetriever)
    if err != nil { log.Fatal("Agent 构建失败:", err) }
    questions := []string{"Eino 框架怎么安装?", "今天杭州天气怎么样?", "Go 的泛型有哪些限制?"}
    for _, q := range questions {
        fmt.Printf("👤 用户
%s

", q)
        resp, err := agent.Invoke(ctx, []*schema.Message{schema.UserMessage(q)})
        if err != nil { fmt.Printf("❌ 处理失败: %v

", err); continue }
        fmt.Printf("🤖 Agent
%s

", resp.Content)
    }
}

Sample output shows the agent automatically selecting the appropriate tool for each query:

👤 用户
Eino 框架怎么安装?

🤖 Agent
[调用 get_knowledge: "Eino 安装方法"]
Eino 安装很简单,执行 go get github.com/cloudwego/eino@latest,确保 Go 版本 ≥ 1.21 即可。

👤 用户
今天杭州天气怎么样?

🤖 Agent
[调用 get_weather: 杭州]
杭州今天多云,20-28°C,建议带把伞。

👤 用户
Go 的泛型有哪些限制?

🤖 Agent
[调用 get_knowledge: "Go 泛型限制"]
Go 泛型的主要限制:不支持泛型方法、类型参数不能用于 type assertion、不能定义泛型类型的别名。

Adding Callbacks for Full Trace

By attaching a compose.HandlerBuilder with OnStartFn and OnEndFn, each node logs its start and end, making the internal decision flow visible.

handler := compose.NewHandlerBuilder().
    OnStartFn(func(ctx context.Context, info *compose.RunInfo, input compose.CallbackInput) context.Context {
        log.Printf("→ [%s] 开始", info.Name)
        return ctx
    }).
    OnEndFn(func(ctx context.Context, info *compose.RunInfo, output compose.CallbackOutput) context.Context {
        log.Printf("← [%s] 结束", info.Name)
        return ctx
    }).
    Build()

agent.Invoke(ctx, messages, compose.WithCallbacks(handler))

In production, replace the simple logger with distributed tracing solutions such as Langfuse or Volcano Engine APMPlus.

Production‑Readiness Adjustments

Vector store : use a clustered, persistent VikingDB deployment or self‑hosted Qdrant instead of the trial version.

Model calls : add circuit‑break, retry, and rate‑limit (e.g., go‑resiliency for circuit‑break, retry‑go for retries).

Conversation history : store context in Redis and trim by token count using a sliding‑window algorithm ( go‑redis).

Monitoring : replace log.Println with distributed tracing (Langfuse or Volcano APMPlus).

Deployment : build a Docker image ( go build -o agent) and run on Kubernetes with HPA for auto‑scaling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ReActTool IntegrationRAGGoDistributed TracingAI AgentOpenAIVector StoreEinoVikingDB
AI Illustrated Series
Written by

AI Illustrated Series

Illustrated hardcore tech: AI, agents, algorithms, databases—one picture worth a thousand words.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.