一文吃透 Eino 工具的核心原理!從 BaseTool 接口、ToolInfo 説明書到 InferTool 實戰,手把手教你寫可運行的 PDF 解析、簡歷評分工具,附帶通用開發模板直接套用~
這篇文章能讓你:
- 搞懂Eino工具的核心概念
- 掌握創建Eino工具的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傳遞參數時的keyjsonschema:"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:直接實現接口(最基礎,手動處理序列化)
適合想深入理解的場景,步驟:
- 定義工具結構體(空結構體就行,因為接口只要求方法)
- 實現Info()方法(返回ToolInfo)
- 實現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件事:
- 從入參結構體的Tag生成ToolInfo(不用手動寫參數規則)
- 自動解析入參JSON(不用寫json.Unmarshal)
- 自動序列化返回結果(不用寫json.Marshal)
步驟:
- 定義入參結構體(帶jsonschema Tag)
- 定義出參結構體(返回給AI的結果)
- 寫工具核心邏輯函數(入參是結構體,出參是結構體+error)
- 用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.go的NewResumAnalysisAgent函數,就是給簡歷分析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工具邏輯完全一致,只是業務不同:
- 入參結構體:
QuestionGenRequest(接收簡歷文本、崗位信息) - 核心函數:
GenerateQuestion(根據簡歷生成面試問題) - 包裝函數:
CreateQuestionGenTool(用InferTool包裝)
5.4 工具在Agent中的調用流程(項目核心)
以「簡歷分析Agent」為例,工具調用的完整流程:
- 用户傳入PDF簡歷路徑
- Agent的Instruction告訴AI:「先調用pdf_to_text工具提取文本,再分析」
- AI生成工具調用指令(比如
{"name":"pdf_to_text","arguments":{"file_path":"/Users/xxx/簡歷.pdf"}}) - Eino框架解析這個指令,調用ToolsNode中的PDF工具
- PDF工具返回轉換後的文本
- 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:運行測試
- 確保項目依賴已安裝:
go mod tidy - 運行
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.go、resume_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()
}
模板使用步驟
第一步:替換模板中的「佔位符」
所有 [工具名稱] 都要替換成你的實際工具名稱(比如 PDFToText、ResumeScore),示例:
- 入參結構體:
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 接口請求搜索服務
- 計算工具:實現具體的計算邏輯(如簡歷評分規則)
第四步:測試工具(單獨調試)
- 在工具文件末尾的
main函數中調用Test[工具名稱]Tool() - 運行命令:
go run tool/[工具文件].go(如go run tool/pdf_parser_tool.go) - 查看日誌輸出,確認工具是否正常返回結果
第五步:集成到 Agent 中
- 在 Agent 的創建函數中,導入你的工具
- 在
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
}
注意事項
- 依賴安裝:如果使用第三方庫(如 PDF 解析庫),需要先執行
go get 庫地址(如go get github.com/unidoc/unipdf/v3) - 參數命名:入參結構體的
json:"參數名"必須小寫,符合 JSON 規範(AI 調用時會用小寫 key) - 序列化 兼容:出參結構體不要用複雜類型(如函數、指針),只保留基礎類型(string、int、bool、slice、map)
- 錯誤處理:核心邏輯中要捕獲可能的錯誤(如文件不存在、解析失敗),並返回具體的錯誤信息(方便調試)
- 工具標識唯一:每個工具的
工具名稱小寫下劃線必須唯一(比如不能有兩個pdf_to_text工具)
和我們一起擁抱AI應用的開發
對AI智能體,AI編程感興趣的朋友可以在掘金私信我,或者直接加我微信:wangzhongyang1993。
後面我還會更新更多跟AI相關的文章,歡迎關注我一起學習。