Skip to content

Commit b69c85d

Browse files
authored
docs: add eino create tool (#1299)
1 parent 7f771b2 commit b69c85d

File tree

6 files changed

+761
-4
lines changed

6 files changed

+761
-4
lines changed
Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
---
2+
Description: ""
3+
date: "2025-04-09"
4+
lastmod: ""
5+
tags: []
6+
title: How to create a tool?
7+
weight: 0
8+
---
9+
10+
## The basic structure of Tool
11+
12+
An agent needs to take two steps to invoke a tool: ① The large model constructs invocation parameters based on the tool's functions and parameter requirements. ② Actually invokes the tool
13+
14+
These two basic steps also require the tool to include two parts:
15+
16+
- Introduction to the functions of the tool and the parameter information needed to invoke this tool.
17+
- Call the interface of this tool
18+
19+
In Eino, the BaseTool interface requires any tool to have a ` Info()` interface that returns tool information, as follows:
20+
21+
```go
22+
type BaseTool interface {
23+
Info(ctx context.Context) (*schema.ToolInfo, error)
24+
}
25+
```
26+
27+
And according to whether the return structure of a tool is streamable after it is invoked, it can be divided into InvokableTool and StreamableTool, which are also defined in the form of interfaces:
28+
29+
```go
30+
type InvokableTool interface {
31+
BaseTool
32+
33+
// InvokableRun call function with arguments in JSON format
34+
InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error)
35+
}
36+
37+
type StreamableTool interface {
38+
BaseTool
39+
40+
StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error)
41+
}
42+
```
43+
44+
## The representation of ToolInfo
45+
46+
In the process of function call in a large model, the large model generates the parameters needed for the function call, which requires the large model to understand whether the generated parameters meet the constraints. In Eino, based on the developers' usage habits and domain standards, it provides ` params map[string]*ParameterInfo` and ` *openapi3.Schema` two ways to express parameter constraints.
47+
48+
### Method 1 - map[string]*ParameterInfo
49+
50+
In the intuitive habits of many developers, the description of parameters can be represented by a map, where the key is the parameter name and the value is the detailed constraint of this parameter. In Eino, ParameterInfo is defined to represent the description of a parameter, as follows:
51+
52+
```go
53+
// watch at: https://github.com/cloudwego/eino/blob/main/schema/tool.go
54+
type ParameterInfo struct {
55+
Type DataType // The type of the parameter.
56+
ElemInfo *ParameterInfo // The element type of the parameter, only for array.
57+
SubParams map[string]*ParameterInfo // The sub parameters of the parameter, only for object.
58+
Desc string // The description of the parameter.
59+
Enum []string // The enum values of the parameter, only for string.
60+
Required bool // Whether the parameter is required.
61+
}
62+
```
63+
64+
For example, a parameter representing "User" can be expressed as:
65+
66+
```go
67+
map[string]*schema.ParameterInfo{
68+
"name": &schema.ParameterInfo{
69+
Type: schema.String,
70+
Required: true,
71+
},
72+
"age": &schema.ParameterInfo{
73+
Type: schema.Integer,
74+
},
75+
"gender": &schema.ParameterInfo{
76+
Type: schema.String,
77+
Enum: []string{"male", "female"},
78+
},
79+
}
80+
```
81+
82+
This representation method is very simple and intuitive, and it is often used when parameters are manually maintained by developers through coding.
83+
84+
### Method 2 - openapi3.Schema
85+
86+
Another common way to represent parameter constraints is ` JSON schema` , which is defined by OAI. [ OpenAPI](https://github.com/OAI/OpenAPI-Specification) is the most commonly used standard, and Eino also supports the use of openapi3.Schema to represent parameter constraints.
87+
88+
The OpenAPI 3 standard offers a wide range of constraints for parameters, and a detailed description can be found in [ OpenAPI 3.03](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object) . In actual use, developers typically do not build this structure themselves, but use some methods to generate it.
89+
90+
#### Generate using GoStruct2ParamsOneOf
91+
92+
Eino provides a way to describe parameter constraints in structures using go tags, and it also offers the GoStruct2ParamsOneOf method to generate parameter constraints for a struct. Its function signature is as follows:
93+
94+
```go
95+
func GoStruct2ParamsOneOf[T any](opts ...Option) (*schema.ParamsOneOf, error)
96+
```
97+
98+
Extract the field name and description of the parameters from T, and the Tag used for extraction is as follows:
99+
100+
- jsonschema: "description=xxx"
101+
- jsonschema: "enum=xxx,enum=yyy,enum=zzz"
102+
- jsonschema: "required"
103+
- json: "xxx,omitempty" => The "omitempty" in json tag represents that it is not required
104+
- Implement a custom parsing method using utils.WithSchemaCustomizer
105+
106+
You can refer to the following examples:
107+
108+
```go
109+
package main
110+
111+
import (
112+
"github.com/cloudwego/eino/components/tool/utils"
113+
)
114+
115+
type User struct {
116+
Name string `json:"name" jsonschema:"required,description=the name of the user"`
117+
Age int `json:"age" jsonschema:"description=the age of the user"`
118+
Gender string `json:"gender" jsonschema:"enum=male,enum=female"`
119+
}
120+
121+
func main() {
122+
params, err := utils.GoStruct2ParamsOneOf[User]()
123+
}
124+
```
125+
126+
This method is generally not invoked by developers, and is often directly used ` utils.GoStruct2ToolInfo()` to build ToolInfo, or directly use ` utils.InferTool()` to directly build a tool. You can refer to the "Convert local functions into tools" section below for more details.
127+
128+
#### Generated through the openapi.json file
129+
130+
Since OpenAPI is a very universal standard, many tools or platforms can export OpenAPI.json files, especially in some HTTP interface management tools. If the tool is a wrapper for some OpenAPI, you can use this method.
131+
132+
See the usage examples in [ eino-examples](https://github.com/cloudwego/eino-examples/blob/main/components/tool/openapi3/main.go#L33)
133+
134+
## Method 1 - Directly implement the interface
135+
136+
Since the definition of a tool is an interface, the most direct way to implement a tool is to implement the interface. Take InvokableTool as an example:
137+
138+
```go
139+
type AddUser struct{}
140+
141+
func (t *AddUser) Info(_ context.Context) (*schema.ToolInfo, error) {
142+
return &schema.ToolInfo{
143+
Name: "add_user",
144+
Desc: "add user",
145+
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
146+
// omit. Refer to the way of constructing params constraints in the above text.
147+
}),
148+
}, nil
149+
}
150+
151+
func (t *AddUser) InvokableRun(_ context.Context, argumentsInJSON string, _ ...tool.Option) (string, error) {
152+
// 1. Deserialize argumentsInJSON and handle options, etc.
153+
user, _ := json.Unmarshal([]byte(argumentsInJSON))
154+
// 2. Handle business logic
155+
// 3. Serialize the result to a string and return it
156+
157+
return `{"msg": "ok"}`, nil
158+
}
159+
```
160+
161+
Since the function call parameters given by the large model are always a string, in the Eino framework, the imported parameter of the tool is also a json serialized into a string. Therefore, this approach requires developers to handle the deserialization of parameters themselves, and the result of the call is also returned as a string.
162+
163+
## Method 2 - Convert local functions into tools
164+
165+
During development, we often need to encapsulate a local function into a tool of Eino, for example, we already have an ` AddUser` method in our code, but to allow the large model to independently decide how to call this method, we need to turn this method into a tool and bind it to the large model.
166+
167+
Eino provides ` NewTool` methods to convert a function into a tool. Additionally, it offers InferTool methods for scenarios where parameter constraints are represented through the tag of a struct, making the build process simpler.
168+
169+
> You can refer to the examples of the following methods:[ cloudwego/eino/components/tool/utils/invokable_func_test.go](https://github.com/cloudwego/eino/blob/main/components/tool/utils/invokable_func_test.go) and [ cloudwego/eino/components/tool/utils/streamable_func_test.go](https://github.com/cloudwego/eino/blob/main/components/tool/utils/streamable_func_test.go) in the unit test. Here, we only take InvokableTool as an example, and StreamableTool also has corresponding construction methods
170+
171+
### Use the NewTool method
172+
173+
When a function satisfies the following function signature, you can use NewTool to turn it into an InvokableTool:
174+
175+
```go
176+
type InvokeFunc[T, D any] func(ctx context.Context, input T) (output D, err error)
177+
```
178+
179+
The method of NewTool is as follows:
180+
181+
```go
182+
// See the code at: github.com/cloudwego/eino/components/tool/utils/invokable_func.go
183+
func NewTool[T, D any](desc *schema.ToolInfo, i InvokeFunc[T, D], opts ...Option) tool.InvokableTool
184+
```
185+
186+
> Similarly, NewStreamTool can create StreamableTool
187+
188+
Take AddUser as an example, you can build it in the following way:
189+
190+
```go
191+
import (
192+
"github.com/cloudwego/eino/components/tool"
193+
"github.com/cloudwego/eino/components/tool/utils"
194+
"github.com/cloudwego/eino/schema"
195+
)
196+
197+
type User struct {
198+
Name string `json:"name"`
199+
Age int `json:"age"`
200+
Gender string `json:"gender"`
201+
}
202+
203+
type Result struct {
204+
Msg string `json:"msg"`
205+
}
206+
207+
func AddUser(ctx context.Context, user *User) (*Result, error) {
208+
// some logic
209+
}
210+
211+
func createTool() tool.InvokableTool {
212+
addUserTool := utils.NewTool(&schema.ToolInfo{
213+
Name: "add_user",
214+
Desc: "add user",
215+
ParamsOneOf: schema.NewParamsOneOfByParams(
216+
map[string]*schema.ParameterInfo{
217+
"name": &schema.ParameterInfo{
218+
Type: schema.String,
219+
Required: true,
220+
},
221+
"age": &schema.ParameterInfo{
222+
Type: schema.Integer,
223+
},
224+
"gender": &schema.ParameterInfo{
225+
Type: schema.String,
226+
Enum: []string{"male", "female"},
227+
},
228+
},
229+
),
230+
}, AddUser)
231+
232+
return addUserTool
233+
}
234+
```
235+
236+
### Use the InferTool method
237+
238+
As we can see from NewTool, the process of building a tool requires us to pass in ToolInfo and InvokeFunc separately. Among them, ToolInfo contains the part of ParamsOneOf, which represents the constraint of the imported parameter of the function. At the same time, the function signature of InvokeFunc also has input parameters, which means: The part of ParamsOneOf and the input parameters of InvokeFunc need to be consistent.
239+
240+
When a function is fully implemented by developers themselves, they need to manually maintain the input parameters and ParamsOneOf to keep them consistent. A more elegant solution is to "directly maintain parameter constraints in the input parameter type definition", which can be referred to the introduction of ` GoStruct2ParamsOneOf` above.
241+
242+
When the parameter constraint information is included in the input parameter type definition, you can use InferTool to implement it. The function signature is as follows:
243+
244+
```go
245+
func InferTool[T, D any](toolName, toolDesc string, i InvokeFunc[T, D], opts ...Option) (tool.InvokableTool, error)
246+
```
247+
248+
Take AddUser as an example:
249+
250+
```go
251+
import (
252+
"github.com/cloudwego/eino/components/tool"
253+
"github.com/cloudwego/eino/components/tool/utils"
254+
"github.com/cloudwego/eino/schema"
255+
)
256+
257+
type User struct {
258+
Name string `json:"name" jsonschema:"required,description=the name of the user"`
259+
Age int `json:"age" jsonschema:"description=the age of the user"`
260+
Gender string `json:"gender" jsonschema:"enum=male,enum=female"`
261+
}
262+
263+
type Result struct {
264+
Msg string `json:"msg"`
265+
}
266+
267+
func AddUser(ctx context.Context, user *User) (*Result, error) {
268+
// some logic
269+
}
270+
271+
func createTool() (tool.InvokableTool, error) {
272+
return utils.InferTool("add_user", "add user", AddUser)
273+
}
274+
```
275+
276+
### Use the InferOptionableTool method
277+
278+
The Option mechanism is a feature provided by Eino for passing dynamic parameters at runtime. For more details, you can refer to [Eino: CallOption capabilities and specification](/docs/eino/core_modules/chain_and_graph_orchestration/call_option_capabilities) . This mechanism is also applicable in custom tools.
279+
280+
When developers want to implement a function that requires custom option parameters, they can use the InferOptionableTool method. Compared to the requirements for function signatures in InferTool, this method's signature adds an option parameter, as follows:
281+
282+
```go
283+
func InferOptionableTool[T, D any](toolName, toolDesc string, i OptionableInvokeFunc[T, D], opts ...Option) (tool.InvokableTool, error)
284+
```
285+
286+
Here is an example (adapted from[ cloudwego/eino/components/tool/utils/invokable_func_test.go](https://github.com/cloudwego/eino/blob/main/components/tool/utils/invokable_func_test.go) ):
287+
288+
```go
289+
import (
290+
"fmt"
291+
"context"
292+
"github.com/cloudwego/eino/components/tool"
293+
"github.com/cloudwego/eino/components/tool/utils"
294+
"github.com/cloudwego/eino/schema"
295+
)
296+
297+
type UserInfoOption struct {
298+
Field1 string
299+
}
300+
301+
func WithUserInfoOption(s string) tool.Option {
302+
return tool.WrapImplSpecificOptFn(func(t *UserInfoOption) {
303+
t.Field1 = s
304+
})
305+
}
306+
307+
func updateUserInfoWithOption(_ context.Context, input *User, opts ...tool.Option) (output *UserResult, err error) {
308+
baseOption := &UserInfoOption{
309+
Field1: "test_origin",
310+
}
311+
// handle option
312+
option := tool.GetImplSpecificOptions(baseOption, opts...)
313+
return &Result{
314+
Msg: option.Field1,
315+
}, nil
316+
}
317+
318+
func useInInvoke() {
319+
ctx := context.Background()
320+
tl, _ := utils.InferOptionableTool("invoke_infer_optionable_tool", "full update user info", updateUserInfoWithOption)
321+
322+
content, _ := tl.InvokableRun(ctx, `{"name": "bruce lee"}`, WithUserInfoOption("hello world"))
323+
324+
fmt.Println(content) // Msg is "hello world", because WithUserInfoOption change the UserInfoOption.Field1
325+
}
326+
```
327+
328+
## Method 3 - Use the tool provided in eino-ext
329+
330+
In addition to the various custom tools that need to be implemented by yourself, there are also many general tools implemented in the eino-ext project that can be used out of the box, such [Tool - Googlesearch](/docs/eino/ecosystem/tool/tool_googlesearch)[Tool - DuckDuckGoSearch](/docs/eino/ecosystem/tool/tool_duckduckgo_search) 、wikipedia、httprequest , etc. You can refer to [https://github.com/cloudwego/eino-ext/tree/main/components/tool](https://github.com/cloudwego/eino-ext/tree/main/components/tool) for various implementations.
331+
332+
## Method 4 - Use the MCP protocol
333+
334+
MCP (Model Context Protocol) is an open model context protocol. Now more and more tools and platforms are exposing their own capabilities to large models based on this protocol. eino can use the callable tools provided by MCP as tools, which will greatly expand the variety of tools.
335+
336+
It's very convenient to use the tools provided by MCP in Eino:
337+
338+
```go
339+
import (
340+
"fmt"
341+
"log"
342+
"github.com/mark3labs/mcp-go/client"
343+
mcpp "github.com/cloudwego/eino-ext/components/tool/mcp"
344+
)
345+
346+
func getMCPTool(ctx context.Context) []tool.BaseTool {
347+
cli, err := client.NewSSEMCPClient("http://localhost:12345/sse")
348+
if err != nil {
349+
log.Fatal(err)
350+
}
351+
err = cli.Start(ctx)
352+
if err != nil {
353+
log.Fatal(err)
354+
}
355+
356+
initRequest := mcp.InitializeRequest{}
357+
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
358+
initRequest.Params.ClientInfo = mcp.Implementation{
359+
Name: "example-client",
360+
Version: "1.0.0",
361+
}
362+
363+
_, err = cli.Initialize(ctx, initRequest)
364+
if err != nil {
365+
log.Fatal(err)
366+
}
367+
368+
tools, err := mcpp.GetTools(ctx, &mcpp.Config{Cli: cli})
369+
if err != nil {
370+
log.Fatal(err)
371+
}
372+
373+
return tools
374+
}
375+
```
376+
377+
> Code reference: [https://github.com/cloudwego/eino-ext/blob/main/components/tool/mcp/examples/mcp.go](https://github.com/cloudwego/eino-ext/blob/main/components/tool/mcp/examples/mcp.go)

content/en/docs/eino/overview/bytedance_eino_practice.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ The following diagram is an application of the generated BuildEinoAgent function
465465

466466
##### APMPlus
467467

468-
If you specify `APMPLUS_APP_KEY` in the .env file during runtime,you can log in to the corresponding account on the [Volcengine APMPlus](https://console.volcengine.com/apmplus-server%22) platform to view the trace and metric details of the requests.
468+
If you specify `APMPLUS_APP_KEY` in the .env file during runtime,you can log in to the corresponding account on the [Volcengine APMPlus](https://console.volcengine.com/apmplus-server) platform to view the trace and metric details of the requests.
469469

470470
<a href="/img/eino/callback_apmplus.gif" target="_blank"><img src="/img/eino/callback_apmplus.gif" width="100%" /></a>
471471

0 commit comments

Comments
 (0)