Browse Source

增加非好友标注功能
优化智能打标逻辑

boweniac 1 week ago
parent
commit
f5f275d52d

+ 90 - 44
crontask/contact_form.go

@@ -1,12 +1,10 @@
 package crontask
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
 	"github.com/google/uuid"
 	"github.com/zeromicro/go-zero/core/logx"
-	"net/http"
 	"strconv"
 	"time"
 	"wechat-api/ent/contact"
@@ -15,6 +13,8 @@ import (
 	"wechat-api/ent/custom_types"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/usagedetail"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/compapi"
 )
 
 type ResponseItem struct {
@@ -168,7 +168,7 @@ func (l *CronTask) analyze() {
 			template = append(template, contactBasicFieldTemplates...)
 		}
 		for receiverID, messages := range usageDetails[botID] {
-			result, _ := openaiRequest(messages, template)
+			result, _ := l.openaiRequest(messages, template)
 			logx.Info("result: ", result)
 			if result == nil {
 				continue
@@ -179,53 +179,99 @@ func (l *CronTask) analyze() {
 
 }
 
-func openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
-	url := "https://toolsapi-debug.gkscrm.com/call_center/form/extract"
-	bodyData := map[string]interface{}{
-		"form_data":    ConvertFormData(template),
-		"chat_history": messages,
-		"external_id":  uuid.New().String(),
-	}
-	logx.Info("bodyData: %+v", bodyData)
-	bodyBytes, err := json.Marshal(bodyData)
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("Content-Type", "application/json")
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.ZS9jnsLPCnmc8L_lu4yaQFp34vwWF1mHlHSBYrY5JVs")
-
-	client := &http.Client{}
-	resp, err := client.Do(req)
-	if err != nil || resp == nil || resp.Body == nil {
-		logx.Error("read body error: ", err)
-		return nil, err
-	}
-
-	logx.Info("err: ", err)
+func (l *CronTask) openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
+	formData := ConvertFormData(template)
+	jsonBytes, err := json.Marshal(formData)
 	if err != nil {
 		return nil, err
 	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
-	}
-
-	//var result []ResponseItem
-	var fullResp struct {
-		Data []ResponseItem `json:"data"`
+	jsonStr := string(jsonBytes)
+	req := &types.CompApiReq{
+		types.CompCtlReq{
+			"form",
+			"",
+			false,
+			"",
+		},
+		types.StdCompApiReq{
+			"gpt-4o",
+			[]types.StdCompMessage{},
+			false,
+			nil,
+		},
+		types.FastGptSpecReq{
+			"",
+			"",
+			"",
+			false,
+			map[string]string{
+				"form_data":    jsonStr,
+				"chat_history": messages,
+				"external_id":  uuid.New().String(),
+			},
+		},
 	}
-	err = json.NewDecoder(resp.Body).Decode(&fullResp)
-	if err != nil {
+	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase("http://new-api.gkscrm.com/v1/"),
+		compapi.WithApiKey("sk-wwttAtdLcTfeF7F2Eb9d3592Bd4c487f8e8fA544D6C4BbA9")).
+		Chat(req)
+	if err == nil && resp != nil && len(resp.Choices) > 0 {
+		//logx.Info("resp.Choices: ", resp.Choices[0].Message.Content)
+		var items []ResponseItem
+		err = json.Unmarshal([]byte(resp.Choices[0].Message.Content), &items)
+		if err != nil {
+			return nil, err
+		}
+		return items, nil
+	} else if resp != nil && len(resp.Choices) == 0 {
 		return nil, err
 	}
-
-	return fullResp.Data, nil
+	//url := "https://toolsapi-debug.gkscrm.com/call_center/form/extract"
+	//bodyData := map[string]interface{}{
+	//	"form_data":    ConvertFormData(template),
+	//	"chat_history": messages,
+	//	"external_id":  uuid.New().String(),
+	//}
+	//logx.Info("bodyData: %+v", bodyData)
+	//bodyBytes, err := json.Marshal(bodyData)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//
+	//req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
+	//if err != nil {
+	//	return nil, err
+	//}
+	//req.Header.Set("Content-Type", "application/json")
+	//req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.ZS9jnsLPCnmc8L_lu4yaQFp34vwWF1mHlHSBYrY5JVs")
+	//
+	//client := &http.Client{}
+	//resp, err := client.Do(req)
+	//if err != nil || resp == nil || resp.Body == nil {
+	//	logx.Error("read body error: ", err)
+	//	return nil, err
+	//}
+	//
+	//logx.Info("err: ", err)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//defer resp.Body.Close()
+	//
+	//if resp.StatusCode != http.StatusOK {
+	//	return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+	//}
+	//
+	////var result []ResponseItem
+	//var fullResp struct {
+	//	Data []ResponseItem `json:"data"`
+	//}
+	//err = json.NewDecoder(resp.Body).Decode(&fullResp)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//
+	//return fullResp.Data, nil
+	return nil, err
 }
 
 func (l *CronTask) UpdateContactFields(botID string, receiverID string, fields []ResponseItem) error {

+ 20 - 20
crontask/init.go

@@ -24,25 +24,25 @@ func NewCronTask(ctx context.Context, svcCtx *svc.ServiceContext) *CronTask {
 
 func ScheduleRun(c *cron.Cron, serverCtx *svc.ServiceContext) {
 
-	l := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("* * * * *", func() {
-		l.sendMsg()
-	})
-
-	sendWx := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("* * * * *", func() {
-		sendWx.sendWx()
-	})
-
-	sendWxOnTimeout := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("* * * * *", func() {
-		sendWxOnTimeout.sendWxOnTimeout()
-	})
-
-	computeStatistic := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("0 * * * *", func() {
-		computeStatistic.computeStatistic()
-	})
+	//l := NewCronTask(context.Background(), serverCtx)
+	//c.AddFunc("* * * * *", func() {
+	//	l.sendMsg()
+	//})
+	//
+	//sendWx := NewCronTask(context.Background(), serverCtx)
+	//c.AddFunc("* * * * *", func() {
+	//	sendWx.sendWx()
+	//})
+	//
+	//sendWxOnTimeout := NewCronTask(context.Background(), serverCtx)
+	//c.AddFunc("* * * * *", func() {
+	//	sendWxOnTimeout.sendWxOnTimeout()
+	//})
+	//
+	//computeStatistic := NewCronTask(context.Background(), serverCtx)
+	//c.AddFunc("0 * * * *", func() {
+	//	computeStatistic.computeStatistic()
+	//})
 
 	//syncWx := NewCronTask(context.Background(), serverCtx)
 	//c.AddFunc("*/30 * * * *", func() {
@@ -50,7 +50,7 @@ func ScheduleRun(c *cron.Cron, serverCtx *svc.ServiceContext) {
 	//})
 
 	contactForm := NewCronTask(context.Background(), serverCtx)
-	c.AddFunc("0 0 * * *", func() {
+	c.AddFunc("@every 1m", func() {
 		contactForm.analyze()
 	})
 

+ 4 - 1
crontask/send_msg.go

@@ -2,6 +2,7 @@ package crontask
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/url"
 	"path"
 	"time"
@@ -224,7 +225,9 @@ func (l *CronTask) sendMsg() {
 			// 这里之前只有文字消息(既 msgtype=1) 目前增加了图片 所以增加了msgtype=2
 			// 所以增加了一个判断,判断发送的内容类型,如果是文字就调用SendTextMsg,如果是图片就调用SendPicMsg
 			if msg.Msgtype == 1 {
-				err = hookClient.SendTextMsg(msg.Toid, msg.Msg, wxInfo.Wxid)
+				msgId := time.Now().UnixNano() / int64(time.Microsecond)
+				l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), msg.Toid, 10*time.Minute)
+				err = hookClient.SendTextMsg(msg.Toid, msg.Msg, wxInfo.Wxid, msgId)
 			} else if msg.Msgtype == 2 {
 				diyfilename := getFileName(msg.Msg)
 				err = hookClient.SendPicMsg(msg.Toid, msg.Msg, diyfilename, wxInfo.Wxid)

+ 4 - 1
crontask/send_wx.go

@@ -3,6 +3,7 @@ package crontask
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"regexp"
 	"strconv"
 	"strings"
@@ -144,7 +145,9 @@ func (l *CronTask) sendWx() {
 					contactInfo, _ := getContactInfo(v.BotWxid, v.ContactWxid)
 					content = varReplace(content, contactInfo)
 				}
-				err = hookClient.SendTextMsg(v.ContactWxid, content, v.BotWxid)
+				msgId := time.Now().UnixNano() / int64(time.Microsecond)
+				l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), v.ContactWxid, 10*time.Minute)
+				err = hookClient.SendTextMsg(v.ContactWxid, content, v.BotWxid, msgId)
 			} else {
 				re := regexp.MustCompile(`[^/]+$`)
 				fileName := re.FindString(v.Content)

+ 2 - 0
desc/wechat/contact.api

@@ -68,6 +68,8 @@ type (
 		//标签搜索开始结束日期
 		StartDate *string `json:"start_date,optional"`
         EndDate *string `json:"end_date,optional"`
+
+        Status *uint8 `json:"status,optional"`
     }
 
     // Contact information response | Contact信息返回体

+ 3 - 1
hook/message.go

@@ -27,7 +27,7 @@ func (h *Hook) ConfigureMsgRecive(isEnable string, url string) (err error) {
 }
 
 // SendTextMsg 发送微信文本消息
-func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
+func (h *Hook) SendTextMsg(wxid, msg, wxWxid string, msgId int64) error {
 	if h.ServerIp == "" || h.ServerIp == "0" {
 		conn, err := h.connWorkPhone()
 		if err != nil {
@@ -73,6 +73,7 @@ func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
 					"ConvId":      wxid,
 					"ContentType": "Text",
 					"Content":     encodedString,
+					"TaskId":      msgId,
 				},
 			}
 		} else {
@@ -83,6 +84,7 @@ func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
 					"FriendId":    wxid,
 					"ContentType": "Text",
 					"Content":     encodedString,
+					"MsgId":       msgId,
 				},
 			}
 		}

+ 1 - 1
internal/handler/routes.go

@@ -1,5 +1,5 @@
 // Code generated by goctl. DO NOT EDIT.
-// goctls v1.10.4
+// goctls v1.10.1
 
 package handler
 

+ 5 - 3
internal/logic/Wxhook/send_text_msg_logic.go

@@ -2,8 +2,10 @@ package Wxhook
 
 import (
 	"context"
+	"fmt"
 	"github.com/suyuan32/simple-admin-common/enum/errorcode"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"time"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/wx"
 	"wechat-api/hook"
@@ -72,9 +74,9 @@ func (l *SendTextMsgLogic) SendTextMsg(req *types.SendTextMsgReq) (resp *types.B
 	} else {
 		hookClient = hook.NewHook(privateIP, adminPort, port)
 	}
-
-	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid)
-
+	msgId := time.Now().UnixNano() / int64(time.Microsecond)
+	l.svcCtx.Rds.Set(l.ctx, fmt.Sprintf("MsgId_FriendId:%d", msgId), *req.Wxid, 10*time.Minute)
+	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid, msgId)
 	if err != nil {
 		l.Errorf("发送微信文本消息失败:%v\n", err)
 		return nil, err

+ 6 - 0
internal/logic/chat/chat_completions_logic.go

@@ -39,6 +39,10 @@ type MismatchChatLogic struct {
 	ChatCompletionsLogic
 }
 
+type FormChatLogic struct {
+	ChatCompletionsLogic
+}
+
 type baseLogicWorkflow interface {
 	AppendAsyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) error
 	DoSyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (*types.CompOpenApiResp, error)
@@ -147,6 +151,8 @@ func (l *ChatCompletionsLogic) getLogicWorkflow(apiKeyObj *ent.ApiKey, req *type
 		err = fmt.Errorf("api agent type not support(%d)", apiKeyObj.Edges.Agent.Type)
 	} else if req.EventType == "mismatch" {
 		wf = &MismatchChatLogic{ChatCompletionsLogic: *l}
+	} else if req.EventType == "form" {
+		wf = &FormChatLogic{ChatCompletionsLogic: *l}
 	} else {
 		wf = &FastgptChatLogic{ChatCompletionsLogic: *l}
 	}

+ 3 - 0
internal/logic/contact/get_contact_list_logic.go

@@ -58,6 +58,9 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 	if req.WxWxid == nil || (req.WxWxid != nil && !isAdmin) {
 		predicates = append(predicates, contact.OrganizationIDEQ(organizationId))
 	}
+	if req.Status == nil {
+		predicates = append(predicates, contact.StatusEQ(*req.Status))
+	}
 
 	layout := "2006-01-02 15:04:05"
 	var startTime, endTime *time.Time

+ 50 - 0
internal/service/MessageHandlers/talk_to_friend_task_result_notice.go

@@ -0,0 +1,50 @@
+package MessageHandlers
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"wechat-api/ent/contact"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone"
+)
+
+type TalkToFriendTaskResultNoticeHandler struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewTalkToFriendTaskResultNoticeHandler(svcCtx *svc.ServiceContext) *TalkToFriendTaskResultNoticeHandler {
+	return &TalkToFriendTaskResultNoticeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+func (f *TalkToFriendTaskResultNoticeHandler) Handler(msg *wechat_ws.MsgJsonObject) error {
+	if msg.MsgType == "TalkToFriendTaskResultNotice" {
+		message := workphone.TalkToFriendTaskResultNoticeMessage{}
+		err := json.Unmarshal([]byte(msg.Message), &message)
+		if err != nil {
+			return err
+		}
+
+		friendId, _ := f.svcCtx.Rds.Get(context.TODO(), fmt.Sprintf("MsgId_FriendId:%d", message.MsgId)).Result()
+
+		if friendId == "" {
+			return nil
+		}
+
+		if message.Code == workphone.EnumErrorCode_InternalError {
+			err = f.svcCtx.DB.Contact.
+				Update().
+				Where(contact.WxWxidEQ(message.WeChatId), contact.WxidEQ(friendId)).
+				SetStatus(2).
+				Exec(context.TODO())
+
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 1 - 0
internal/types/types.go

@@ -1078,6 +1078,7 @@ type ContactListReq struct {
 	//标签搜索开始结束日期
 	StartDate *string `json:"start_date,optional"`
 	EndDate   *string `json:"end_date,optional"`
+	Status    *uint8  `json:"status,optional"`
 }
 
 // Contact information response | Contact信息返回体

+ 2 - 0
internal/utils/compapi/base.go

@@ -65,6 +65,8 @@ func (me *Client) getClientActFace(clientType string) (clientActionFace, error)
 	switch clientType {
 	case "mismatch":
 		actFace = &MismatchClient{StdClient: StdClient{Client: me}}
+	case "form":
+		actFace = &FormClient{StdClient: StdClient{Client: me}}
 	default:
 		actFace = &FastgptClient{StdClient: StdClient{Client: me}}
 	}

+ 89 - 0
internal/utils/compapi/form.go

@@ -0,0 +1,89 @@
+package compapi
+
+import (
+	"time"
+	"wechat-api/internal/types"
+
+	"github.com/openai/openai-go"
+)
+
+type FormClient struct {
+	StdClient
+}
+
+// Generate the JSON schema at initialization time
+var FormResponseSchema = GenerateSchema[FormResponse]()
+
+type FormResponse struct {
+	DataIndex string   `json:"dataIndex" jsonschema_description:"表单 id"`
+	Value     []string `json:"value" jsonschema_description:" 值列表"`
+}
+
+type FormList struct {
+	Values []FormResponse `json:"values" jsonschema_description:"表单列表"`
+}
+
+/*
+
+"response_format":{"type":"json_object"}}
+
+{"type":"json_schema",
+	"json_schema":{
+		"description":"从通话记录中提取表单","name":"keyword_schema","schema":{
+			"$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false,"properties":{
+			"keywords":{
+				"description":"关键词库","items":{"type":"string"},"type":"array"},"regular":{"description":"正则表达式","items":{"type":"string"},"type":"array"},"similar_reply":{"description":"类似回复","items":{"type":"string"},"type":"array"},"swear_words":{"description":"脏话列表","items":{"type":"string"},"type":"array"},"user_intent":{"description":"用户意图","type":"string"},"user_mood":{"description":"用户情绪","type":"string"}},"required":["user_intent","similar_reply","keywords","regular","user_mood","swear_words"],"type":"object"},"strict":true}}
+*/
+
+func (me *FormClient) BuildRequest(req *types.CompApiReq) error {
+	nowTime := time.Now().Format("2006-01-02 15:04:05")
+	//bytes, err := json.Marshal(req.Variables["form_data"])
+	//if err != nil {
+	//	return err
+	//}
+	//form_data := string(bytes)
+
+	//先重构message
+	newMessSlice := make([]types.StdCompMessage, 2)
+	newMessSlice[0] = types.StdCompMessage{Role: "system", Content: `# 任务
+请帮助user从通话记录中提取表单值,并返回一个JSON格式的表单值。
+
+# 背景信息
+` + nowTime + `
+
+# 返回值示例
+* 如表单类型为 input、autoComplete、textarea,返回示例:["表单值"]
+* 如表单类型为 radio、select,返回示例:["值1"]
+* 如表单类型为 checkbox,返回示例:["值1", "值2"]
+* 如表单类型为 cascader,返回示例:["一级值1", "二级值3"]
+* 如表单类型为 date,返回示例:["2025-01-01"]`}
+
+	newMessSlice[1] = types.StdCompMessage{Role: "user", Content: `# 表单数据
+` + req.Variables["form_data"] + `
+
+# 聊天记录
+` + req.Variables["chat_history"]}
+
+	//再构造ResponseFormat
+	if !IsOpenaiModel(req.Model) {
+		newMessSlice[1].Content = newMessSlice[1].Content.(string) + `
+# 请以下方的json结构输出
+[{
+    "dataIndex": str, # 表单ID
+    "value": list[str] # 表单值列表
+}]`
+		req.ResponseFormat = openai.ResponseFormatJSONObjectParam{Type: "json_object"}
+	} else {
+		schemaParam := openai.ResponseFormatJSONSchemaJSONSchemaParam{
+			Name:        "keyword_schema",
+			Description: openai.String("从通话记录中提取表单"),
+			Schema:      FormResponseSchema,
+			Strict:      openai.Bool(true),
+		}
+		req.ResponseFormat = openai.ResponseFormatJSONSchemaParam{JSONSchema: schemaParam}
+	}
+	//req.Model = oldModel
+	req.Messages = newMessSlice
+
+	return nil
+}

+ 1 - 0
wechat.go

@@ -65,6 +65,7 @@ func main() {
 			//ws.RegisterMessageHandler(ic.OnMessage)
 			ws.RegisterMessageHandler(MessageHandlers.NewChatroomPushNoticeHandler(ctx).Handler)
 			ws.RegisterMessageHandler(MessageHandlers.NewFriendPushNoticeHandler(ctx).Handler)
+			ws.RegisterMessageHandler(MessageHandlers.NewTalkToFriendTaskResultNoticeHandler(ctx).Handler)
 		}
 		logx.Info("注册个微处理通道~")
 	}