观测/数据上报/AI 框架/Eino
Eino
更新于: 2026-06-24 15:45:09
本文介绍如何上报 Eino 运行过程的 Trace 数据到扣子罗盘,实现对 AI 应用的监控和分析。
Eino 是一个基于 Golang 语言开发的大模型应用开发框架。扣子罗盘基于 Eino Callback 机制与 Eino 集成,捕获 Eino 应用执行过程的 Trace 数据。上报 Trace 数据到扣子罗盘后,你可以在扣子罗盘中进行可视化分析。本集成方案适用于需要监控 Eino 应用执行链路、分析 AI 交互全链路情况的场景。
在上报 Trace 数据前,你需要正确配置环境变量,以确保 Eino 应用能够正常调用 OpenAI 模型,并能正确发送 Trace 数据到指定的扣子罗盘空间中。环境变量配置格式及说明如下:
|
环境变量 |
说明 |
|---|---|
|
COZELOOP_API_TOKEN |
设置上报数据时所需的扣子罗盘认证信息,即配置为扣子罗盘的个人访问令牌或服务访问令牌。获取步骤,请参考配置个人访问令牌、配置服务访问令牌。 |
|
COZELOOP_WORKSPACE_ID |
配置为扣子罗盘工作空间 ID。获取步骤,请参考获取扣子罗盘工作空间 ID。 |
|
OPENAI_BASE_URL |
配置 OpenAI 模型的服务地址。
|
|
OPENAI_API_KEY |
配置 OpenAI 模型的 API Key。
|
|
OPENAI_MODEL_NAME |
配置 OpenAI 模型名称。 |
完成上述配置后,你可以调用 OpenAI 模型开展 AI 对话,并通过扣子罗盘 SDK 上报 Trace 数据到扣子罗盘。
在本示例中,Eino 框架基于 OpenAI 模型,实现了简单的用户信息查询和房产推荐功能。同时,借助扣子罗盘 SDK 将模型调用、工具执行等关键步骤的 Trace 数据自动上报到扣子罗盘,便于开发者监控和分析 AI 交互全链路的执行情况。
import (
"context"
"errors"
"os"
clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/callbacks"
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/cozeloop-go"
"github.com/cloudwego/eino-examples/internal/gptr"
"github.com/cloudwego/eino-examples/internal/logs"
)
func main() {
openAIBaseURL := os.Getenv("OPENAI_BASE_URL")
openAIAPIKey := os.Getenv("OPENAI_API_KEY")
modelName := os.Getenv("OPENAI_MODEL_NAME")
cozeloopApiToken := os.Getenv("COZELOOP_API_TOKEN")
cozeloopWorkspaceID := os.Getenv("COZELOOP_WORKSPACE_ID") // use cozeloop trace, from https://loop.coze.cn/open/docs/cozeloop/go-sdk#4a8c980e
userQuery := "我叫zhangsan, 邮箱是zhangsan@xxx.com, 查询我的信息"
// init cozeloop client
ctx := context.Background()
var handlers []callbacks.Handler
if cozeloopApiToken != "" && cozeloopWorkspaceID != "" {
client, err := cozeloop.NewClient(
cozeloop.WithAPIToken(cozeloopApiToken),
cozeloop.WithWorkspaceID(cozeloopWorkspaceID),
)
if err != nil {
panic(err)
}
defer client.Close(ctx)
handlers = append(handlers, clc.NewLoopHandler(client))
}
// 只需在程序开始处设置一次GlobalHandler,后续就可以自动检测并上报
callbacks.AppendGlobalHandlers(handlers...)
// 后续完成Eino Graph逻辑,这里是一个查询用户信息的model+tool的示例
systemTpl := `你是一名房产经纪人,结合用户的薪酬和工作,使用 user_info API,为其提供相关的房产信息。邮箱是必须的`
chatTpl := prompt.FromMessages(schema.FString,
schema.SystemMessage(systemTpl),
schema.MessagesPlaceholder("message_histories", true),
schema.UserMessage("{query}"),
)
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: openAIBaseURL,
APIKey: openAIAPIKey,
Model: modelName,
Temperature: gptr.Of(float32(0.7)),
})
if err != nil {
logs.Fatalf("NewChatModel failed, err=%v", err)
}
userInfoTool := utils.NewTool(
&schema.ToolInfo{
Name: "user_info",
Desc: "根据用户的姓名和邮箱,查询用户的公司、职位、薪酬信息",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"name": {
Type: "string",
Desc: "用户的姓名",
},
"email": {
Type: "string",
Desc: "用户的邮箱",
},
}),
},
func(ctx context.Context, input *userInfoRequest) (output *userInfoResponse, err error) {
return &userInfoResponse{
Name: input.Name,
Email: input.Email,
Company: "Awesome company",
Position: "CEO",
Salary: "9999",
}, nil
})
info, err := userInfoTool.Info(ctx)
if err != nil {
logs.Fatalf("Get ToolInfo failed, err=%v", err)
}
err = chatModel.BindTools([]*schema.ToolInfo{info})
if err != nil {
logs.Fatalf("BindTools failed, err=%v", err)
}
toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
Tools: []tool.BaseTool{userInfoTool},
})
if err != nil {
logs.Fatalf("NewToolNode failed, err=%v", err)
}
takeOne := compose.InvokableLambda(func(ctx context.Context, input []*schema.Message) (*schema.Message, error) {
if len(input) == 0 {
return nil, errors.New("input is empty")
}
return input[0], nil
})
const (
nodeModel = "node_model"
nodeTools = "node_tools"
nodeTemplate = "node_template"
nodeConverter = "node_converter"
)
branch := compose.NewStreamGraphBranch(func(ctx context.Context, input *schema.StreamReader[*schema.Message]) (string, error) {
defer input.Close()
msg, err := input.Recv()
if err != nil {
return "", err
}
if len(msg.ToolCalls) > 0 {
return nodeTools, nil
}
return compose.END, nil
}, map[string]bool{compose.END: true, nodeTools: true})
graph := compose.NewGraph[map[string]any, *schema.Message]()
_ = graph.AddChatTemplateNode(nodeTemplate, chatTpl)
_ = graph.AddChatModelNode(nodeModel, chatModel)
_ = graph.AddToolsNode(nodeTools, toolsNode)
_ = graph.AddLambdaNode(nodeConverter, takeOne)
_ = graph.AddEdge(compose.START, nodeTemplate)
_ = graph.AddEdge(nodeTemplate, nodeModel)
_ = graph.AddBranch(nodeModel, branch)
_ = graph.AddEdge(nodeTools, nodeConverter)
_ = graph.AddEdge(nodeConverter, compose.END)
r, err := graph.Compile(ctx)
if err != nil {
logs.Fatalf("Compile failed, err=%v", err)
}
out, err := r.Invoke(ctx, map[string]any{"query": userQuery})
if err != nil {
logs.Fatalf("Invoke failed, err=%v", err)
}
logs.Infof("result content: %v", out.Content)
}
type userInfoRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type userInfoResponse struct {
Name string `json:"name"`
Email string `json:"email"`
Company string `json:"company"`
Position string `json:"position"`
Salary string `json:"salary"`
}
上报 Trace 数据后,你可以在扣子罗盘的 Trace 页面,找到并单击目标 Span,查看上报的 Trace 数据。
关于上报 Eino Trace 数据的更多示例,请参考 Eino。