go语言Dify api /Deepseek/接入飞书机器人_dify接入飞书机器人
本文演示Dify api /Deepseek/接入飞书机器人
- 准备工作
-
- 打开[飞书开发者后台](https://open.feishu.cn/app)
- 搭建服务端长链接飞书机器人通过长连接获取用户私聊发的消息
- 实现调用deepseek或dify的api的代码逻辑
- 运行效果
准备工作
开通了接收消息权限的飞书机器人,例如我希望用户跟飞书机器人私聊,就需要开通这个权限:读取用户发给机器人的单聊消息
准备好飞书机器人的API key 和Secret
deepseek-v3的api key+secret:这里获取 ,一开始有10元的免费额度,趁能充多充点,经常不让充值。
自己部署一下dify,推荐使用docker-compose方式,这个有很多教程就不赘述了
打开飞书开发者后台
-
创建企业自用应用,本文以创建好的 dify消息测试为例
-
等待企业管理员审核通过后打开应用 添加机器人
-
开通应用权限应用和用户权限都需要开 (方便测试我把消息组全部已开通-可能不需要这么多)
4. 新建版本发布,初次创建版本需要飞书管理员审批,按确认就好
搭建服务端长链接飞书机器人通过长连接获取用户私聊发的消息
我们使用长连接的方式接收用户消息,需要在飞书开发者后台中配置一下应用,见 配置回调订阅方式 代码如下:
package agent_v1import (\"context\"\"encoding/json\"\"errors\"\"fmt\"lark \"github.com/larksuite/oapi-sdk-go/v3\"larkcore \"github.com/larksuite/oapi-sdk-go/v3/core\"larkevent \"github.com/larksuite/oapi-sdk-go/v3/event\"\"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher\"larkim \"github.com/larksuite/oapi-sdk-go/v3/service/im/v1\"larkws \"github.com/larksuite/oapi-sdk-go/v3/ws\"\"log\"\"regexp\"\"strings\")var sent map[string]struct{} // 这里简单去个重 实际使用要自己再写去重部分// 飞书消息过来Content字段值是{\\\"text\\\":\\\"早上好~\\\"}这样的,需要再解析一下type Text struct {Text string `json:\"text\"`}// 飞书应用信息var AppID = \"\"var AppSecret = \"\"// cleanMessage 去除消息中的特殊字符和前缀func cleanMessage(message string) string {// 使用正则表达式匹配 @xxx 格式的内容re := regexp.MustCompile(`@\\S+\\s*`)// 全局替换所有匹配到的 @xxx 格式的内容cleanedMessage := re.ReplaceAllString(message, \"\")return strings.TrimSpace(cleanedMessage)}// Callback 处理接收到用户消息的事件func Callback() {sent = make(map[string]struct{})// 注册事件回调,OnP2MessageReceiveV1 为接收消息 v2.0;OnCustomizedEvent 内的 message 为接收消息 v1.0。NewEventDispatcher()里的两个参数都填空字符串eventHandler := dispatcher.NewEventDispatcher(\"\", \"\").OnP2ChatAccessEventBotP2pChatEnteredV1(func(ctx context.Context, event *larkim.P2ChatAccessEventBotP2pChatEnteredV1) error {fmt.Printf(\"[ OnP2ChatAccessEventBotP2pChatEnteredV1 access ], data: %s\\n\", larkcore.Prettify(event))return nil}).OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {msgId := *event.Event.Message.MessageIdif _, ok := sent[msgId]; ok {return nil} else {sent[msgId] = struct{}{}}fmt.Printf(\"接收飞书消息 data: %s\\n\", larkcore.Prettify(event))fmt.Println(\"接收飞书内容\", *event.Event.Message.Content) // content中就是用户发过来的消息内容var text Texterr := json.Unmarshal([]byte(*event.Event.Message.Content), &text)if err != nil {return err}fmt.Println(\"飞书内容解析:\", text.Text)// 这里可以把用户输入发给deepseek或者dify并接收其响应,具体实现后面讲/*resp, e := CallDeepSeekAPI(text.Text)if e != nil {return e}*/textContent := cleanMessage(text.Text)if *event.Event.Message.ChatType == \"group\" && text.Text == textContent {log.Printf(\"群里请@对应机器人\")return errors.New(\"群里请@对应机器人\")}resp, err := ChatMessages(textContent, *event.Event.Sender.SenderId.UnionId)if err != nil {log.Printf(\"请求dify失败: %v\", err)return err}fmt.Println(\"dify 返回消息:\", resp)// 这里组织飞书机器人发送给用户的消息的content格式,跟接收到消息的一样,也是{\\\"text\\\":\\\"say something\\\"}var contentStruct struct {Text string `json:\"text\"`}contentStruct.Text = respcontent, err := json.Marshal(contentStruct)if err != nil {log.Fatalf(\"发送给用户消息解析失败: %v\", err)return err}fmt.Println(\"飞书机发送给用户的消息:\", string(content))//接收者receiveID := *event.Event.Sender.SenderId.OpenIdif *event.Event.Message.ChatType == \"group\" {receiveID = *event.Event.Message.ChatId}// 这个messages是机器人发送消息函数,见下方messages(\"飞书应用token\", receiveID, *event.Event.Message.MessageType, string(content))return nil}).// 处理 bot_p2p_chat_entered_v1 事件// 处理 im.message.message_read_v1 事件OnCustomizedEvent(\"im.message.message_read_v1\", func(ctx context.Context, event *larkevent.EventReq) error {fmt.Printf(\"[ im.message.message_read_v1 access ], data: %s\\n\", string(event.Body))// 在这里添加对 im.message.message_read_v1 事件的处理逻辑// 例如记录已读状态或执行其他操作var eventData map[string]interface{}err := json.Unmarshal(event.Body, &eventData)if err != nil {fmt.Printf(\"Failed to unmarshal event data: %v\\n\", err)return err}fmt.Printf(\"Event data: %v\\n\", eventData)return nil}).//im:message.p2p_msg:readonly 这个先不用管OnCustomizedEvent(\"\", func(ctx context.Context, event *larkevent.EventReq) error {fmt.Printf(\"[ OnCustomizedEvent access ], type: message, data: %s\\n\", string(event.Body))return nil})// 创建Clientcli := larkws.NewClient(AppID, AppSecret,larkws.WithEventHandler(eventHandler),larkws.WithLogLevel(larkcore.LogLevelDebug),)// 启动客户端 保持一个长链接err := cli.Start(context.Background())if err != nil {panic(err)}}// 发送消息func messages(token, receiveID, msgType, content string) {client := lark.NewClient(AppID, AppSecret)// 创建请求对象receiveIDType := \"open_id\"if strings.HasPrefix(receiveID, \"oc\") { // 这里我简单区分了一下群聊和个人receiveIDType = \"chat_id\"}req := larkim.NewCreateMessageReqBuilder().ReceiveIdType(receiveIDType).Body(larkim.NewCreateMessageReqBodyBuilder().ReceiveId(receiveID).MsgType(msgType).Content(content).Build()).Build()resp, err := client.Im.Message.Create(context.Background(), req, larkcore.WithTenantAccessToken(token))if err != nil {log.Printf(\"飞书发送回答到用户 err : %v\", err)return}// 服务端错误处理if !resp.Success() {log.Printf(\"飞书发送回答到用户 resp err : %d,%s,%s\", resp.Code, resp.Msg, resp.RequestId())return}// 业务处理//fmt.Println(larkcore.Prettify(resp))//fmt.Println(string(resp.RawBody))}
实现调用deepseek或dify的api的代码逻辑
Deepseek-v3 API调用代码
在这里插入代码package deepseekimport (\"bytes\"\"encoding/json\"\"fmt\"\"io\"\"net/http\"\"net/http/httputil\")var (// DeepSeek-R1 API 的配置DeepSeekAPIURL = \"https://api.deepseek.com/chat/completions\" // 直接用这个就行DeepSeekAPIKey = \"你的key\")// DeepSeek-R1 API 请求数据结构type DeepSeekRequest struct {Model string `json:\"model\"`Messages []RoleContent `json:\"messages\"`Stream bool `json:\"stream\"`}type RoleContent struct {Role string `json:\"role\"`Content string `json:\"content\"`}// DeepSeek-R1 API 响应数据结构type DeepSeekResponse struct {Choices []struct {Message struct {Content string `json:\"content\"`} `json:\"message\"`} `json:\"choices\"`}// 调用 DeepSeek-R1 APIfunc CallDeepSeekAPI(msg string) (string, error) {requestBody := DeepSeekRequest{Model: \"deepseek-chat\",Messages: []RoleContent{{Role: \"system\", Content: \"You are a helpful assistant.\"}, // 这里可以自行修改{Role: \"user\", Content: msg}, // msg就是用户发的消息},Stream: false, // 这里先不用流式输出}requestBytes, err := json.Marshal(requestBody)if err != nil {return \"\", err}req, err := http.NewRequest(\"POST\", DeepSeekAPIURL, bytes.NewBuffer(requestBytes))if err != nil {return \"\", err}req.Header.Set(\"Authorization\", \"Bearer \"+DeepSeekAPIKey)req.Header.Set(\"Content-Type\", \"application/json\")// 这里我dump了一下请求看发的是否正确 可以删掉dump, _ := httputil.DumpRequest(req, true)fmt.Println(string(dump))// 发请求client := &http.Client{}resp, err := client.Do(req)if err != nil {return \"\", err}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {return \"\", err}// 解析响应var deepSeekResponse DeepSeekResponseif err := json.Unmarshal(body, &deepSeekResponse); err != nil {return \"\", err}// 拿content返回if len(deepSeekResponse.Choices) > 0 {return deepSeekResponse.Choices[0].Message.Content, nil}return \"\", fmt.Errorf(\"no response from DeepSeek API\")}片
Dify api调用方法
如何在dify中接入大模型并制作一个问答机器人参考:dify文档
点击【发布】之后,去【访问api】页面,右上角有一个在这里插入图片描述 点击这个API密钥保存下来
调用代码如下:
package agent_v1import (\"bytes\"\"encoding/json\"\"errors\"\"fmt\"\"io\"\"log\"\"net/http\"\"strings\")type ChatMessageRequest struct {Inputs map[string]interface{} `json:\"inputs\"`Query string `json:\"query\"`ResponseMode string `json:\"response_mode\"`ConversationID string `json:\"conversation_id,omitempty\"`User string `json:\"user\"`}type ChatMessageResponse struct {ID string `json:\"id\"`Answer string `json:\"answer\"`ConversationID string `json:\"conversation_id\"`CreatedAt int `json:\"created_at\"`}const (DifyBaseURL = \"\" // 这里是你的dify服务地址DifyApiKey = \"\" // dify提供的api密钥ChatMsgPath = \"/chat-messages\")func ChatMessages(msg, unionId string) (string, error) {requestData := ChatMessageRequest{Inputs: map[string]interface{}{},Query: msg,ResponseMode: \"streaming\",User: \"飞书用户:\" + unionId, //企业内跨应用用户唯一id}// 将请求数据序列化为 JSONrequestBody, err := json.Marshal(requestData)if err != nil {fmt.Errorf(\"failed to marshal request data: %v\", err)return \"\", errors.New(\"dify body 解析失败\")}// 创建 HTTP 请求req, err := http.NewRequest(\"POST\", DifyBaseURL+ChatMsgPath, bytes.NewBuffer(requestBody))if err != nil {log.Fatalf(\"Failed to create request: %v\", err)return \"\", errors.New(\"dify请求失败\" + err.Error())}// 设置请求头req.Header.Set(\"Authorization\", \"Bearer \"+DifyApiKey)req.Header.Set(\"Content-Type\", \"application/json\")// 发送请求client := &http.Client{}// 这里dump了一下看发送请求是否正确,可以删掉//dump, _ := httputil.DumpRequest(req, true)//fmt.Println(\"发送请求到dify:\" + string(dump))resp, err := client.Do(req)if err != nil {log.Fatalf(\"Failed to send request: %v\", err)}defer resp.Body.Close()// 读取响应body, err := io.ReadAll(resp.Body)if err != nil {log.Fatalf(\"Failed to read response body: %v\", err)return \"\", errors.New(\"dify响应 body失败\" + err.Error())}// 检查响应状态码if resp.StatusCode != http.StatusOK {log.Printf(\"dify 响应 status code err : %d,%s\", resp.StatusCode, string(body))return \"\", errors.New(\"dify 响应 code err\")}// 解析响应体中的多个事件var events []map[string]interface{}if strings.Contains(resp.Header.Get(\"Content-Type\"), \"text/event-stream\") {lines := strings.Split(string(body), \"\\n\")for _, line := range lines {if strings.HasPrefix(line, \"data: \") {var event map[string]interface{}if err := json.Unmarshal([]byte(line[5:]), &event); err != nil {log.Printf(\"dify 响应body 解析err Event: %v, Event Data: %s\", err, line[5:])continue}events = append(events, event)}}} else {var res ChatMessageResponseif err = json.Unmarshal(body, &res); err != nil {log.Printf(\"dify 响应body解析结构体失败 err : %v, Response Body: %s\", err, string(body))return \"\", errors.New(\"dify 响应bofy解析结构体\")}fmt.Println(\"Answer:\", res.Answer)return res.Answer, nil}// 查找最后一个 agent_message 事件var answer stringfor _, event := range events {if eventType, ok := event[\"event\"].(string); ok && eventType == \"agent_message\" {if ans, ok := event[\"answer\"].(string); ok {answer += ans}}}fmt.Println(\"dify返回答案:\", answer)return answer, nil}
// startFeishuBot 启动飞书机器人客户端 main方法里调用func startFeishuBot() {go agent_v1.Callback()}
先按服务端运行项目建立长连接后,飞书配置事件回调,事件添加上面提到的事件,消息,卡片几组;回调配置(方便测试添加了所有的)
运行效果
赶紧去试试吧,如果你觉得文章对你有用麻烦大帅哥大美女们点个关注和收藏,祝大家工作顺利,身体健康,您的支持是我更新的持续动力,感谢!