博客 / 詳情

返回

Eino 工具開發避坑指南:小白也能看懂的概念拆解 + 實操教程

一文吃透 Eino 工具的核心原理!從 BaseTool 接口、ToolInfo 説明書到 InferTool 實戰,手把手教你寫可運行的 PDF 解析、簡歷評分工具,附帶通用開發模板直接套用~

這篇文章能讓你:

  1. 搞懂Eino工具的核心概念
  2. 掌握創建Eino工具的4種方式
  3. 拆解項目中工具的實現邏輯
  4. 從零寫出可運行的Eino工具

1. 核心概念通俗講(先懂原理再看代碼)

Eino的「工具」本質是讓AI Agent能調用的「外部功能模塊」,比如PDF解析、搜索、計算等。先搞懂這幾個核心概念,後面看代碼就不懵了:

1.1 三個核心接口(工具的「身份證」)

Eino用接口定義工具的規範,就像「必須滿足這幾個條件才能當工具」

  • BaseTool:所有工具的「基礎要求」,必須能返回自己的「説明書」(ToolInfo)
  • InvokableTool:「同步工具」,調用後等待結果返回(比如PDF解析,調用後等文本輸出)
  • StreamableTool:「流式工具」,調用後持續返回結果(比如實時聊天、視頻流,項目中用得少)
  • 後兩者二選一

代碼對應:

// 基礎工具接口(必須實現)
type BaseTool interface {
    Info(ctx context.Context) (*schema.ToolInfo, error) // 返回工具説明書
}

// 同步工具接口(項目中最常用)
type InvokableTool interface {
    BaseTool // 繼承基礎要求
    // 同步執行工具,入參是JSON字符串,返回結果字符串
    InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
}

1.2 ToolInfo:工具的「説明書」

AI Agent要知道「這個工具能幹嘛、要傳什麼參數」,全靠ToolInfo。比如PDF解析工具的説明書:

  • 名稱:pdf_to_text(AI調用時用的標識)
  • 描述:將本地PDF轉為純文本,支持可複製的PDF(告訴AI什麼時候用)
  • 參數:需要傳入PDF的絕對路徑(告訴AI要傳什麼)

1.3 ToolsNode:工具的「組合器」

一個Agent可能需要多個工具(比如簡歷分析Agent需要PDF解析+簡歷評分工具),ToolsNode就是把多個工具打包成一個「工具包」,讓Agent能統一調用,不用單獨管理每個工具。

1.4 Option:工具的「動態配置」

比如調用工具時想設置超時時間、重試次數,就用Option傳遞(類似給工具傳「額外參數」),項目中偶爾用到,後面講示例。

2. ToolInfo的兩種表示方式(工具的「説明書」)

ToolInfo的核心是「告訴AI參數規則」,Eino提供兩種方式,項目中用的是第二種(結構體+Tag),重點掌握:

2.1 方式1:手動寫參數規則(適合簡單場景)

直接用map定義參數名、類型、是否必填,比如「添加用户」工具:

// 手動構建參數規則
params := map[string]*schema.ParameterInfo{
    "name": &schema.ParameterInfo{
        Type:     schema.String, // 參數類型:字符串
        Required: true,          // 必須傳
        Desc:     "用户姓名",     // 描述
    },
    "age": &schema.ParameterInfo{
        Type: schema.Integer, // 參數類型:整數
        Desc: "用户年齡",
    },
}

// 構建ToolInfo
toolInfo := &schema.ToolInfo{
    Name:        "add_user",       // 工具名稱(AI調用時用)
    Desc:        "添加新用户到系統", // 工具功能描述
    ParamsOneOf: schema.NewParamsOneOfByParams(params), // 綁定參數規則
}

2.2 方式2:結構體+Tag(項目中常用,推薦)

用Golang結構體定義參數,通過Tag標註規則(不用手動寫map),Eino會自動轉換成ToolInfo,小白只需記住Tag的含義:

  • json:"參數名":AI傳遞參數時的key
  • jsonschema:"required":該參數必須傳
  • jsonschema:"description=xxx":參數描述(告訴AI)
  • jsonschema:"enum=xxx,enum=yyy":參數只能選枚舉值

示例(項目中PDF解析工具的參數定義):

// PDF解析工具的入參結構體
type PDFToTextRequest struct {
    // 參數名:file_path,必須傳,描述是PDF的絕對路徑
    FilePath string `json:"file_path" jsonschema:"required,description=本地PDF文件的絕對路徑"`
    // 參數名:split_page,可選,描述是是否按頁分割,默認false
    SplitPage bool `json:"split_page" jsonschema:"description=是否按頁面分割文本,默認false"`
}

然後用Eino提供的工具函數,自動生成ToolInfo(不用自己寫map),後面創建工具時會用到。

3. 創建Eino工具的4種方式(從簡單到複雜)

重點掌握「方式2」(InferTool),因為項目中所有工具都用的這個!其他方式瞭解即可。

3.1 方式1:直接實現接口(最基礎,手動處理序列化)

適合想深入理解的場景,步驟:

  1. 定義工具結構體(空結構體就行,因為接口只要求方法)
  2. 實現Info()方法(返回ToolInfo)
  3. 實現InvokableRun()方法(工具核心邏輯,處理入參和返回結果)

示例:簡單的「加法工具」

package main

import (
    "context"
    "encoding/json"
    "log"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
    "github.com/cloudwego/eino/schema"
)

// 1. 定義工具結構體(空的,只是為了實現接口)
type AddTool struct{}

// 2. 實現Info()方法:返回工具説明書
func (t *AddTool) Info(_ context.Context) (*schema.ToolInfo, error) {
    // 定義參數規則(方式1:手動寫map)
    params := map[string]*schema.ParameterInfo{
        "a": &schema.ParameterInfo{Type: schema.Integer, Required: true, Desc: "第一個數字"},
        "b": &schema.ParameterInfo{Type: schema.Integer, Required: true, Desc: "第二個數字"},
    }
    return &schema.ToolInfo{
        Name:        "add",
        Desc:        "計算兩個整數的和",
        ParamsOneOf: schema.NewParamsOneOfByParams(params),
    }, nil
}

// 3. 實現InvokableRun()方法:工具核心邏輯
func (t *AddTool) InvokableRun(_ context.Context, argsJSON string, _ ...tool.Option) (string, error) {
    // 第一步:解析入參(AI傳的JSON字符串轉成結構體)
    type Input struct {
        A int `json:"a"`
        B int `json:"b"`
    }
    var input Input
    err := json.Unmarshal([]byte(argsJSON), &input)
    if err != nil {
        return "", err // 參數解析失敗返回錯誤
    }

    // 第二步:核心業務邏輯(計算和)
    sum := input.A + input.B

    // 第三步:返回結果(結構體轉JSON字符串)
    type Output struct {
        Sum int `json:"sum"`
        Msg string `json:"msg"`
    }
    output := Output{Sum: sum, Msg: "計算成功"}
    outputJSON, _ := json.Marshal(output)
    return string(outputJSON), nil
}

// 測試工具
func main() {
    ctx := context.Background()
    addTool := &AddTool{}
    // 調用工具:傳入{"a":1,"b":2}
    result, err := addTool.InvokableRun(ctx, `{"a":1,"b":2}`)
    if err != nil {
        log.Fatal(err)
    }
    log.Println("結果:", result) // 輸出:{"sum":3,"msg":"計算成功"}
}

解析:這種方式需要手動處理「JSON轉結構體」和「結構體轉JSON」,項目中不用,因為有更簡單的方式。

3.2 方式2:用InferTool(項目常用,自動處理序列化)

Eino提供utils.InferTool函數,能自動幫你做3件事:

  1. 從入參結構體的Tag生成ToolInfo(不用手動寫參數規則)
  2. 自動解析入參JSON(不用寫json.Unmarshal)
  3. 自動序列化返回結果(不用寫json.Marshal)

步驟:

  1. 定義入參結構體(帶jsonschema Tag)
  2. 定義出參結構體(返回給AI的結果)
  3. 寫工具核心邏輯函數(入參是結構體,出參是結構體+error)
  4. 用InferTool包裝成Eino工具

示例:用InferTool實現「加法工具」(對比方式1,簡潔太多)

package main

import (
    "context"
    "log"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
)

// 1. 入參結構體(帶Tag,告訴Eino參數規則)
type AddInput struct {
    A int `json:"a" jsonschema:"required,description=第一個數字"`
    B int `json:"b" jsonschema:"required,description=第二個數字"`
}

// 2. 出參結構體(工具返回的結果)
type AddOutput struct {
    Sum int    `json:"sum" description="兩個數的和"`
    Msg string `json:"msg" description="執行狀態"`
}

// 3. 工具核心邏輯函數(普通Golang函數)
func addFunc(ctx context.Context, input *AddInput) (*AddOutput, error) {
    sum := input.A + input.B
    return &AddOutput{
        Sum: sum,
        Msg: "計算成功",
    }, nil
}

// 4. 用InferTool包裝成Eino工具
func CreateAddTool() tool.InvokableTool {
    // 函數參數:工具名稱、工具描述、核心邏輯函數
    addTool, err := utils.InferTool("add", "計算兩個整數的和", addFunc)
    if err != nil {
        log.Fatalf("創建工具失敗:%v", err)
    }
    return addTool
}

// 測試工具
func main() {
    ctx := context.Background()
    addTool := CreateAddTool()
    // 調用工具:傳入JSON字符串
    result, err := addTool.InvokableRun(ctx, `{"a":3,"b":5}`)
    if err != nil {
        log.Fatal(err)
    }
    log.Println("結果:", result) // 輸出:{"sum":8,"msg":"計算成功"}
}

解析:這就是項目中工具的實現方式!比如pdfParserTool.go中的CreatePDFToTextTool,完全遵循這個邏輯,後面會拆解。

3.3 方式3:帶Option的工具(動態配置)

如果工具需要「動態參數」(比如超時時間、重試次數),用InferOptionableTool,步驟和方式2類似,多了Option定義:

示例:帶超時配置的加法工具

// 1. 定義Option結構體(動態配置項)
type AddToolOptions struct {
    Timeout int `json:"timeout"` // 超時時間(秒)
}

// 2. 定義Option函數(給外部設置配置用)
func WithTimeout(timeout int) tool.Option {
    return tool.WrapImplSpecificOptFn(func(o *AddToolOptions) {
        o.Timeout = timeout
    })
}

// 3. 核心邏輯函數(多了opts參數)
func addWithOptionFunc(ctx context.Context, input *AddInput, opts ...tool.Option) (*AddOutput, error) {
    // 默認配置
    defaultOpts := &AddToolOptions{Timeout: 5}
    // 合併外部傳入的配置
    tool.GetImplSpecificOptions(defaultOpts, opts...)
    
    // 這裏可以用defaultOpts.Timeout做超時處理
    sum := input.A + input.B
    return &AddOutput{Sum: sum, Msg: fmt.Sprintf("超時時間:%d秒", defaultOpts.Timeout)}, nil
}

// 4. 用InferOptionableTool包裝
func CreateAddWithOptionTool() tool.InvokableTool {
    tool, err := utils.InferOptionableTool("add_with_option", "帶超時配置的加法工具", addWithOptionFunc)
    if err != nil {
        log.Fatal(err)
    }
    return tool
}

// 測試:傳入超時時間3秒
func main() {
    ctx := context.Background()
    addTool := CreateAddWithOptionTool()
    result, _ := addTool.InvokableRun(ctx, `{"a":2,"b":4}`, WithTimeout(3))
    log.Println(result) // 輸出:{"sum":6,"msg":"超時時間:3秒"}
}

3.4 方式4:使用Eino-ext現成工具(開箱即用)

Eino有現成的工具庫(比如搜索、維基百科、HTTP請求),不用自己寫,直接導入使用,示例:

import (
    "github.com/cloudwego/eino-ext/components/tool/duckduckgosearch"
)

// 創建 duckduckgo 搜索工具
func CreateSearchTool() tool.InvokableTool {
    searchTool, err := duckduckgosearch.NewTool()
    if err != nil {
        log.Fatal(err)
    }
    return searchTool
}

4. ToolsNode:工具的「組合器」(項目中怎麼用)

一個Agent可能需要多個工具,比如「簡歷分析Agent」需要:

  • PDF解析工具(提取簡歷文本)
  • 簡歷評分工具(給簡歷打分)

ToolsNode就是把這兩個工具打包,讓Agent統一調用。

4.1 創建ToolsNode的步驟

package main

import (
    "context"
    "log"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/components/tool"
)

// 假設已經創建了兩個工具
func CreatePDFTool() tool.InvokableTool { /* 省略實現,參考3.2 */ }
func CreateScoreTool() tool.InvokableTool { /* 省略實現 */ }

func main() {
    ctx := context.Background()
    
    // 1. 創建單個工具
    pdfTool := CreatePDFTool()
    scoreTool := CreateScoreTool()
    
    // 2. 用ToolsNode組合工具
    toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
        Tools: []tool.BaseTool{pdfTool, scoreTool}, // 放入工具包
    })
    if err != nil {
        log.Fatal(err)
    }
    
    // 3. 給Agent配置這個ToolsNode(項目中核心用法)
    // 後面Agent章節會講,這裏知道ToolsNode是給Agent用的就行
}

4.2 ToolsNode在項目中的應用

項目中agent/resumAgent.goNewResumAnalysisAgent函數,就是給簡歷分析Agent配置ToolsNode:

// 簡歷分析Agent的創建
func NewResumAnalysisAgent() adk.Agent {
    ctx := context.Background()

    a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "ResumAnalysisAgent",
        Description: "解析簡歷並分析",
        Instruction: "你的任務是分析簡歷...",
        Model: chat.CreatOpenAiChatModel(ctx),
        // 配置ToolsNode:給Agent綁定PDF解析工具
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                Tools: []tool.BaseTool{tool2.CreatePDFToTextTool()}, // 這裏就是ToolsNode
            },
        },
    })
    return a
}

解析:項目中沒有單獨創建ToolsNode變量,而是直接在Agent配置中傳入ToolsNodeConfig,本質是一樣的——給Agent綁定工具包。

5. 拆解項目中的工具實現(理論對接實戰)

看看tool文件夾下的文件是怎麼對應前面講的理論的,以核心文件為例:

5.1 項目工具結構回顧

tool/
├── pdfParserTool.go    # PDF解析工具(核心)
├── gen_question.go     # 面試問題生成工具
├── gen_AnswerEvalTool.go # 回答評估工具
└── ask_for_clarification.go # 交互工具

5.2 拆解pdfParserTool.go(PDF解析工具)

這個文件完全遵循「方式2:InferTool」,逐行解析:

package tool2

import (
    "context"
    "log"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
)

// 1. 入參結構體(帶Tag,工具的參數規則)
type PDFToTextRequest struct {
    FilePath string `json:"file_path" jsonschema:"required,description=本地PDF文件的絕對路徑"`
    SplitPage bool `json:"split_page" jsonschema:"description=是否按頁面分割文本,默認false"`
}

// 2. 出參結構體(工具返回結果)
type PDFToTextResult struct {
    Content string `json:"content" description="PDF轉換後的文本"`
    Pages   []string `json:"pages" description="按頁分割的文本(SplitPage為true時返回)"`
    Msg     string `json:"msg" description="執行狀態"`
}

// 3. 核心邏輯函數(PDF轉文本的實際操作)
func ConvertPDFToText(ctx context.Context, req *PDFToTextRequest) (*PDFToTextResult, error) {
    // 這裏是真正的PDF解析邏輯:打開文件→讀取內容→轉換文本
    // 省略具體實現(和Eino工具規範無關,是業務邏輯)
    var content string
    var pages []string
    
    if req.SplitPage {
        return &PDFToTextResult{
            Content: "",
            Pages:   pages,
            Msg:     "轉換成功(按頁分割)",
        }, nil
    }
    return &PDFToTextResult{
        Content: content,
        Pages:   nil,
        Msg:     "轉換成功",
    }, nil
}

// 4. 用InferTool包裝成Eino工具(對外提供)
func CreatePDFToTextTool() tool.InvokableTool {
    // 工具名稱:pdf_to_text,描述:PDF轉純文本,核心函數:ConvertPDFToText
    pdfTool, err := utils.InferTool(
        "pdf_to_text", 
        "將本地PDF文件轉換為純文本,僅支持文本型PDF,不支持掃描件、加密PDF",
        ConvertPDFToText,
    )
    if err != nil {
        log.Fatalf("創建PDF工具失敗:%v", err)
    }
    fmt.Println("✅ PDF工具初始化完成")
    return pdfTool
}

對應理論

  • PDFToTextRequest:入參結構體+Tag → 對應「ToolInfo的方式2」
  • ConvertPDFToText:核心邏輯函數 → 對應「方式2的核心函數」
  • CreatePDFToTextTool:用InferTool包裝 → 對應「方式2創建工具」
  • 最終返回tool.InvokableTool → 對應「同步工具接口」

5.3 拆解gen_question.go(問題生成工具)

和PDF工具邏輯完全一致,只是業務不同:

  1. 入參結構體:QuestionGenRequest(接收簡歷文本、崗位信息)
  2. 核心函數:GenerateQuestion(根據簡歷生成面試問題)
  3. 包裝函數:CreateQuestionGenTool(用InferTool包裝)

5.4 工具在Agent中的調用流程(項目核心)

以「簡歷分析Agent」為例,工具調用的完整流程:

  1. 用户傳入PDF簡歷路徑
  2. Agent的Instruction告訴AI:「先調用pdf_to_text工具提取文本,再分析」
  3. AI生成工具調用指令(比如{"name":"pdf_to_text","arguments":{"file_path":"/Users/xxx/簡歷.pdf"}}
  4. Eino框架解析這個指令,調用ToolsNode中的PDF工具
  5. PDF工具返回轉換後的文本
  6. Agent接收文本,繼續執行分析邏輯

6. 從零實現自己的工具(實操演練)

跟着做,你會創建一個「簡歷評分工具」,並集成到項目中:

6.1 步驟1:創建工具文件tool/gen_resume_score.go

package tool2

import (
    "context"
    "log"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
)

// 1. 入參結構體:接收簡歷文本
type ResumeScoreRequest struct {
    ResumeText string `json:"resume_text" jsonschema:"required,description=簡歷轉換後的純文本"`
    Position   string `json:"position" jsonschema:"required,description=目標崗位(如:後端開發)"`
}

// 2. 出參結構體:返回評分和建議
type ResumeScoreResponse struct {
    Score    int    `json:"score" description="簡歷評分(0-100分)"`
    Strength string `json:"strength" description="簡歷優勢"`
    Suggest  string `json:"suggest" description="改進建議"`
    Msg      string `json:"msg" description="執行狀態"`
}

// 3. 核心邏輯:給簡歷評分
func ScoreResume(ctx context.Context, req *ResumeScoreRequest) (*ResumeScoreResponse, error) {
    // 簡單的評分邏輯(實際項目中可以用AI或更復雜的規則)
    resumeLen := len(req.ResumeText)
    var score int
    
    // 規則:文本長度≥500字得80+,≥300字得60+,否則低於60
    if resumeLen >= 500 {
        score = 85
    } else if resumeLen >= 300 {
        score = 65
    } else {
        score = 50
    }
    
    // 生成優勢和建議
    strength := "文本完整度達標"
    suggest := "建議補充量化成果(如:負責XX項目,提升XX效率)"
    
    return &ResumeScoreResponse{
        Score:    score,
        Strength: strength,
        Suggest:  suggest,
        Msg:      "評分成功",
    }, nil
}

// 4. 包裝成Eino工具
func CreateResumeScoreTool() tool.InvokableTool {
    scoreTool, err := utils.InferTool(
        "resume_score", // 工具名稱
        "根據簡歷文本和目標崗位,給簡歷打分並提供改進建議", // 工具描述
        ScoreResume, // 核心邏輯函數
    )
    if err != nil {
        log.Fatalf("創建簡歷評分工具失敗:%v", err)
    }
    log.Println("✅ 簡歷評分工具初始化完成")
    return scoreTool
}

6.2 步驟2:將工具添加到Agent中(修改agent/resum`e`Agent.go

給簡歷分析Agent添加「評分工具」:

func NewResumAnalysisAgent() adk.Agent {
    ctx := context.Background()

    a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "ResumAnalysisAgent",
        Description: "一個可以解析簡歷pdf分析簡歷的智能體",
        Instruction: `你是一名資深的簡歷分析專家,負責對用户的簡歷進行分析:
1. 先使用pdf_to_text工具提取文本(如果用户提供PDF路徑);
2. 再使用resume_score工具給簡歷打分;
3. 最終輸出分析結果、評分和改進建議。`,
        Model: chat.CreatOpenAiChatModel(ctx),
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                // 新增評分工具
                Tools: []tool.BaseTool{tool2.CreatePDFToTextTool(), tool2.CreateResumeScoreTool()},
            },
        },
        MaxIterations: 10,
    })
    if err != nil {
        log.Fatal(fmt.Errorf("failed to create chatmodel: %w", err))
    }

    return a
}

6.3 步驟3:運行測試

  1. 確保項目依賴已安裝:go mod tidy
  2. 運行main.go,傳入PDF路徑,Agent會自動調用「PDF解析工具」和「評分工具」,輸出結果。

7. 常見問題與排查(小白避坑)

7.1 依賴安裝失敗

報錯:cannot find module providing package github.com/cloudwego/eino/xxx

解決:

go mod tidy # 自動整理依賴
go get github.com/cloudwego/eino@latest # 安裝最新版Eino

7.2 工具調用時參數解析失敗

報錯:json: cannot unmarshal object into Go value of type xxx

原因:AI傳入的參數JSON和工具的入參結構體不匹配

排查:

  • 檢查入參結構體的json:"參數名"是否和AI調用的key一致
  • 確保必填參數都傳了(沒傳會報錯)

7.3 工具不被Agent調用

原因:Agent的Instruction沒説清楚「什麼時候用工具」

解決:在Agent的Instruction中明確工具調用邏輯,比如:

Instruction: `當用户提供PDF路徑時,必須先調用pdf_to_text工具提取文本;
提取完成後,調用resume_score工具打分;
最後根據兩個工具的結果生成分析報告。`,

7.4 序列化失敗

報錯:unsupported type for JSON marshaling

原因:出參結構體中有不能轉JSON的類型(比如函數、指針)

解決:出參結構體只保留基礎類型(string、int、bool、slice、map)

Eino 工具開發通用模板

這份模板是基於 Eino 框架「utils.InferTool 方式」(項目中最常用、最簡潔)打造的通用模板,所有工具都能套用這個結構,只需修改「入參、出參、核心邏輯」3個部分,複製粘貼即可使用。

模板文件結構

建議新建文件命名規範:tool/[工具功能]_tool.go(如 pdf_parser_tool.goresume_score_tool.go

// 包名:根據項目實際的 tool 目錄調整(比如你的項目用 tool2,就改成 package tool2)
package tool

import (
        "context"
        "fmt"
        "log"

        "github.com/cloudwego/eino/components/tool"
        "github.com/cloudwego/eino/components/tool/utils"
)

// ======================================
// 第一步:定義工具入參(必須修改!)
// 説明:
// 1. 結構體字段對應工具需要的參數
// 2. json:"參數名":AI 調用工具時傳遞參數的 key(必須小寫,符合 JSON 規範)
// 3. jsonschema 標籤:告訴 AI 參數規則(required 表示必填,description 是參數説明)
// ======================================
type [工具名稱]Input struct {
        // 示例字段1:必填參數(根據實際需求修改字段名、類型、描述)
        Param1 string `json:"param1" jsonschema:"required,description=參數1的功能説明(比如:PDF文件絕對路徑)"`
        // 示例字段2:可選參數(去掉 required 即可)
        Param2 int `json:"param2" jsonschema:"description=參數2的功能説明(比如:超時時間,默認3秒)"`
        // 示例字段3:枚舉參數(限制只能選指定值)
        Param3 string `json:"param3" jsonschema:"description=參數3的功能説明(比如:輸出格式),enum=json,enum=text"`
}

// ======================================
// 第二步:定義工具出參(必須修改!)
// 説明:
// 1. 結構體字段對應工具返回的結果
// 2. description 標籤:説明字段含義(方便 AI 理解結果)
// 3. 只保留基礎類型(string、int、bool、slice、map),避免序列化失敗
// ======================================
type [工具名稱]Output struct {
        // 示例字段1:核心結果(比如 PDF 轉換後的文本、評分結果)
        Result string `json:"result" description="工具執行的核心結果"`
        // 示例字段2:狀態信息(比如執行成功/失敗説明)
        Msg string `json:"msg" description="執行狀態説明"`
        // 示例字段3:附加信息(比如耗時、額外建議)
        Extra map[string]interface{} `json:"extra" description="附加信息(可選)"`
}

// ======================================
// 第三步:工具核心邏輯(必須修改!)
// 函數命名規範:[工具名稱]Func(如 PDFToTextFunc、ResumeScoreFunc)
// 入參:context + 入參結構體指針
// 出參:出參結構體指針 + error(執行失敗返回錯誤,成功返回 nil)
// ======================================
func [工具名稱]Func(ctx context.Context, input *[工具名稱]Input) (*[工具名稱]Output, error) {
        // --------------------------
        // 可選:設置默認值(處理可選參數)
        // --------------------------
        if input.Param2 == 0 { // 如果用户沒傳 Param2,設置默認值 3
                input.Param2 = 3
        }
        if input.Param3 == "" { // 如果用户沒傳 Param3,設置默認值 json
                input.Param3 = "json"
        }

        // --------------------------
        // 核心業務邏輯(重點修改這裏)
        // 示例:模擬一個簡單的工具邏輯(替換成你的實際功能)
        // --------------------------
        log.Printf("工具開始執行:param1=%s, param2=%d, param3=%s", input.Param1, input.Param2, input.Param3)

        // 模擬業務處理(比如 PDF 解析、搜索、計算等)
        coreResult := fmt.Sprintf("處理成功!輸入參數:%s(超時時間:%d秒,輸出格式:%s)", input.Param1, input.Param2, input.Param3)

        // 構建附加信息(可選)
        extraInfo := map[string]interface{}{
                "cost_time": "200ms", // 模擬耗時
                "tip":       "這是附加提示信息",
        }

        // --------------------------
        // 返回結果(固定格式,不用改)
        // --------------------------
        return &[工具名稱]Output{
                Result: coreResult,
                Msg:    "工具執行成功",
                Extra:  extraInfo,
        }, nil
}

// ======================================
// 第四步:包裝成 Eino 工具(無需修改!)
// 函數命名規範:Create[工具名稱]Tool(如 CreatePDFToTextTool)
// 作用:將核心邏輯函數包裝成 Eino 可識別的 InvokableTool
// ======================================
func Create[工具名稱]Tool() tool.InvokableTool {
        // 調用 Eino 工具函數,自動處理序列化、ToolInfo 生成
        toolInstance, err := utils.InferTool(
                "[工具名稱小寫下劃線]", // 工具唯一標識(AI 調用時用,比如 "pdf_to_text")
                "[工具功能描述]",       // 告訴 AI 這個工具能幹嘛(比如 "將本地 PDF 文件轉換為純文本")
                [工具名稱]Func,        // 綁定第三步的核心邏輯函數
        )
        if err != nil {
                // 工具創建失敗直接退出(避免後續報錯)
                log.Fatalf("[%s] 工具創建失敗:%v", "[工具名稱]", err)
        }

        // 日誌提示(可選,方便調試)
        log.Printf("✅ [%s] 工具初始化完成(AI 可調用標識:%s)", "[工具名稱]", "[工具名稱小寫下劃線]")
        return toolInstance
}

// ======================================
// 第五步:測試工具(可選,用於單獨調試)
// 説明:單獨運行這個文件,測試工具是否正常工作
// ======================================
func Test[工具名稱]Tool() {
        // 1. 創建上下文
        ctx := context.Background()

        // 2. 創建工具實例
        testTool := Create[工具名稱]Tool()

        // 3. 構造測試入參(JSON 格式,對應第一步的入參結構體)
        testInputJSON := `{
                "param1": "測試參數1",
                "param2": 5,
                "param3": "text"
        }`

        // 4. 調用工具
        result, err := testTool.InvokableRun(ctx, testInputJSON)
        if err != nil {
                log.Fatalf("工具測試失敗:%v", err)
        }

        // 5. 輸出結果
        log.Printf("\n工具測試成功!返回結果:\n%s", result)
}

// 測試入口(單獨運行時執行)
// 命令:go run tool/[工具文件].go
func main() {
        Test[工具名稱]Tool()
}

模板使用步驟

第一步:替換模板中的「佔位符」

所有 [工具名稱] 都要替換成你的實際工具名稱(比如 PDFToTextResumeScore),示例:

  • 入參結構體:PDFToTextInput(原 [工具名稱]Input
  • 出參結構體:PDFToTextOutput(原 [工具名稱]Output
  • 核心函數:PDFToTextFunc(原 [工具名稱]Func
  • 包裝函數:CreatePDFToTextTool(原 Create[工具名稱]Tool
  • 工具標識:"pdf_to_text"(原 "[工具名稱小寫下劃線]"
  • 工具描述:"將本地 PDF 文件轉換為純文本,支持可複製 PDF,不支持掃描件"(原 "[工具功能描述]"

第二步:修改「入參/出參結構體」

根據你的工具需求,刪除/新增字段,示例:

  • 如果是「簡歷評分工具」,入參可能是 ResumeText(簡歷文本)、Position(目標崗位)
  • 出參可能是 Score(評分)、Strength(優勢)、Suggest(建議)

第三步:編寫「核心業務邏輯」

[工具名稱]Func 中的「模擬業務邏輯」替換成你的實際功能,比如:

  • PDF 解析:調用 PDF 處理庫(如 github.com/unidoc/unipdf/v3)提取文本
  • 搜索工具:調用 HTTP 接口請求搜索服務
  • 計算工具:實現具體的計算邏輯(如簡歷評分規則)

第四步:測試工具(單獨調試)

  1. 在工具文件末尾的 main 函數中調用 Test[工具名稱]Tool()
  2. 運行命令:go run tool/[工具文件].go(如 go run tool/pdf_parser_tool.go
  3. 查看日誌輸出,確認工具是否正常返回結果

第五步:集成到 Agent 中

  1. 在 Agent 的創建函數中,導入你的工具
  2. ToolsConfig.ToolsNodeConfig.Tools 中添加工具實例,示例:
// 簡歷分析 Agent 中添加 PDF 解析工具
import "your-project/tool"

func NewResumAnalysisAgent() adk.Agent {
    ctx := context.Background()

    a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        // ... 其他配置(名稱、描述、模型等)
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                // 加入你的工具
                Tools: []tool.BaseTool{tool.CreatePDFToTextTool()},
            },
        },
    })
    return a
}

注意事項

  1. 依賴安裝:如果使用第三方庫(如 PDF 解析庫),需要先執行 go get 庫地址(如 go get github.com/unidoc/unipdf/v3
  2. 參數命名:入參結構體的 json:"參數名" 必須小寫,符合 JSON 規範(AI 調用時會用小寫 key)
  3. 序列化 兼容:出參結構體不要用複雜類型(如函數、指針),只保留基礎類型(string、int、bool、slice、map)
  4. 錯誤處理:核心邏輯中要捕獲可能的錯誤(如文件不存在、解析失敗),並返回具體的錯誤信息(方便調試)
  5. 工具標識唯一:每個工具的 工具名稱小寫下劃線 必須唯一(比如不能有兩個 pdf_to_text 工具)

和我們一起擁抱AI應用的開發

AI智能體,AI編程感興趣的朋友可以在掘金私信我,或者直接加我微信:wangzhongyang1993。

後面我還會更新更多跟AI相關的文章,歡迎關注我一起學習

user avatar freetalen 頭像 u_15690955 頭像 shishangdexiaomaju 頭像 shanliangdeyanjing 頭像 tina_tang 頭像 u_15741872 頭像 sevencode 頭像 xcye 頭像 maventalker 頭像 u_16380863 頭像 u_16213695 頭像 u_16099330 頭像
13 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.