瀏覽代碼

Merge branch 'develop/compapi_porting_v1.02' into debug

* develop/compapi_porting_v1.02:
  1.compapi结构调整 2.移植新的置信度接口

# Conflicts:
#	internal/logic/chat/chat_completions_logic.go
#	internal/utils/compapi/base.go
boweniac 1 天之前
父節點
當前提交
f558b8624f

+ 142 - 114
internal/logic/chat/chat_completions_logic.go

@@ -16,6 +16,7 @@ import (
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/compapi"
 	"wechat-api/internal/utils/contextkey"
+	"wechat-api/internal/utils/typekit"
 
 	"wechat-api/ent/custom_types"
 	"wechat-api/ent/predicate"
@@ -25,6 +26,13 @@ import (
 	"github.com/zeromicro/go-zero/core/logx"
 )
 
+type baseLogicWorkflow interface {
+	AppendAsyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) error
+	DoSyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (*types.CompOpenApiResp, error)
+	AppendUsageDetailLog(authToken string, req *types.CompApiReq, resp *types.CompOpenApiResp) error
+	AdjustRequest(req *types.CompApiReq, apiKeyObj *ent.ApiKey)
+}
+
 type ChatCompletionsLogic struct {
 	logx.Logger
 	ctx    context.Context
@@ -39,15 +47,32 @@ type MismatchChatLogic struct {
 	ChatCompletionsLogic
 }
 
-type FormChatLogic struct {
+type IntentChatLogic struct {
 	ChatCompletionsLogic
 }
 
-type baseLogicWorkflow interface {
-	AppendAsyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) error
-	DoSyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (*types.CompOpenApiResp, error)
-	AppendUsageDetailLog(authToken string, req *types.CompApiReq, resp *types.CompOpenApiResp) error
-	AdjustRequest(req *types.CompApiReq, apiKeyObj *ent.ApiKey)
+/*
+扩展LogicChat工厂方法
+返回根据不同EventType相关的扩展LogicChat的baseLogicWorkflow接口形式
+
+每增加一个新的扩展LogicChat结构,需要在此函数中增加相应的创建语句
+*/
+func (l *ChatCompletionsLogic) getLogicWorkflow(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (baseLogicWorkflow, error) {
+	var (
+		err error
+		wf  baseLogicWorkflow
+	)
+
+	if apiKeyObj.Edges.Agent.Type != 2 {
+		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 == "intent" {
+		wf = &IntentChatLogic{ChatCompletionsLogic: *l}
+	} else {
+		wf = &FastgptChatLogic{ChatCompletionsLogic: *l}
+	}
+	return wf, err
 }
 
 func NewChatCompletionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ChatCompletionsLogic {
@@ -57,27 +82,6 @@ func NewChatCompletionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *C
 		svcCtx: svcCtx}
 }
 
-func (l *FastgptChatLogic) AdjustRequest(req *types.CompApiReq, apiKeyObj *ent.ApiKey) {
-
-	l.ChatCompletionsLogic.AdjustRequest(req, apiKeyObj) //先父类的参数调整
-
-	if req.EventType != "fastgpt" {
-		return
-	}
-	if len(req.Model) > 0 {
-		if req.Variables == nil {
-			req.Variables = make(map[string]string)
-		}
-		req.Variables["model"] = req.Model
-	}
-
-	if len(req.ChatId) > 0 && len(req.FastgptChatId) == 0 {
-		req.FastgptChatId = req.ChatId
-	} else if len(req.ChatId) == 0 && len(req.FastgptChatId) > 0 {
-		req.ChatId = req.FastgptChatId
-	}
-}
-
 func (l *ChatCompletionsLogic) ChatCompletions(req *types.CompApiReq) (asyncMode bool, resp *types.CompOpenApiResp, err error) {
 	// todo: add your logic here and delete this line
 
@@ -92,39 +96,15 @@ func (l *ChatCompletionsLogic) ChatCompletions(req *types.CompApiReq) (asyncMode
 	if !ok {
 		return asyncMode, nil, errors.New("content get auth info err")
 	}
-	
-	//微调apiKeyObj的openaikey
-	//apiKeyObjAdjust(req.EventType, req.WorkId, apiKeyObj)
-
-	/*
-		fmt.Println("=========================================")
-		fmt.Printf("In ChatCompletion Get Token Info:\nKey:'%s'\n", apiKeyObj.Key)
-		fmt.Printf("Auth Token:'%s'\n", apiKeyObj.Key)
-		fmt.Printf("ApiKey AgentID:%d\n", apiKeyObj.AgentID)
-		fmt.Printf("ApiKey APIBase:'%s'\n", apiKeyObj.Edges.Agent.APIBase)
-		fmt.Printf("ApiKey APIKey:'%s'\n", apiKeyObj.Edges.Agent.APIKey)
-		fmt.Printf("ApiKey Type:%d\n", apiKeyObj.Edges.Agent.Type)
-		fmt.Printf("ApiKey Model:'%s'\n", apiKeyObj.Edges.Agent.Model)
-		fmt.Printf("EventType:'%s'\n", req.EventType)
-		fmt.Printf("req.ChatId:'%s VS req.FastgptChatId:'%s'\n", req.ChatId, req.FastgptChatId)
-		fmt.Println("=========================================")
-	*/
 
 	//根据请求产生相关的工作流接口集
 	wf, err := l.getLogicWorkflow(apiKeyObj, req)
 	if err != nil {
 		return false, nil, err
 	}
-	/*
-		switch wf.(type) {
-		case *MismatchChatLogic:
-			fmt.Println("MismatchChatLogic Flow.....")
-		case *FastgptChatLogic:
-			fmt.Println("FastgptChatLogic Flow.....")
-		default:
-			fmt.Println("Other Flow.....")
-		}
-	*/
+
+	//请求前临时观察相关参数
+	//PreChatVars(req, apiKeyObj, wf)
 
 	//微调部分请求参数
 	wf.AdjustRequest(req, apiKeyObj)
@@ -143,24 +123,6 @@ func (l *ChatCompletionsLogic) ChatCompletions(req *types.CompApiReq) (asyncMode
 	return asyncMode, resp, err
 }
 
-func (l *ChatCompletionsLogic) getLogicWorkflow(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (baseLogicWorkflow, error) {
-	var (
-		err error
-		wf  baseLogicWorkflow
-	)
-
-	if apiKeyObj.Edges.Agent.Type != 2 {
-		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}
-	}
-	return wf, err
-}
-
 func (l *ChatCompletionsLogic) AdjustRequest(req *types.CompApiReq, apiKeyObj *ent.ApiKey) {
 
 	if len(req.EventType) == 0 {
@@ -182,50 +144,16 @@ func (l *ChatCompletionsLogic) AdjustRequest(req *types.CompApiReq, apiKeyObj *e
 }
 
 func (l *ChatCompletionsLogic) DoSyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) (*types.CompOpenApiResp, error) {
-	//return compapi.NewFastgptChatCompletions(l.ctx, apiKeyObj.Edges.Agent.APIKey, apiKeyObj.Edges.Agent.APIBase, req)
 	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase(apiKeyObj.Edges.Agent.APIBase),
 		compapi.WithApiKey(apiKeyObj.Edges.Agent.APIKey)).
 		Chat(req)
 
-	/*
-		if err != nil {
-			return nil, err
-		}
-		if req.EventType == "mismatch" {
-
-			client := compapi.MismatchClient{}
-			taskData := ent.CompapiAsynctask{}
-			taskData.ID = 1234
-			taskData.ResponseChatItemID = req.ResponseChatItemId
-			taskData.EventType = req.EventType
-			taskData.ChatID = req.ChatId
-			var err error
-			taskData.ResponseRaw, err = resp.ToString()
-			if err != nil {
-				fmt.Println(err)
-				return nil, err
-			}
-			var bs []byte
-			bs, err = client.CallbackPrepare(&taskData)
-			if err != nil {
-				fmt.Println(err)
-				return nil, err
-			}
-			fmt.Println(string(bs))
-			nres := map[string]string{}
-			err = json.Unmarshal(bs, &nres)
-			if err != nil {
-				fmt.Println(err)
-				return nil, err
-			}
-			fmt.Println(typekit.PrettyPrint(nres))
+	if err != nil {
+		return nil, err
+	}
+	//以下临时测试case
+	//humanSeeValidResult(l.ctx, req, resp)
 
-			res := compapi.MismatchResponse{}
-			err = compapi.NewChatResult(resp).ParseContentAs(&res)
-			fmt.Println(err)
-			fmt.Println(typekit.PrettyPrint(res))
-		}
-	*/
 	return resp, err
 }
 
@@ -359,6 +287,106 @@ func (l *ChatCompletionsLogic) sumTotalTokensByAuthToken(authToken string) (uint
 	return totalTokens, err
 }
 
+func (l *FastgptChatLogic) AdjustRequest(req *types.CompApiReq, apiKeyObj *ent.ApiKey) {
+
+	l.ChatCompletionsLogic.AdjustRequest(req, apiKeyObj) //先父类的参数调整
+
+	if req.EventType != "fastgpt" {
+		return
+	}
+	if len(req.Model) > 0 {
+		if req.Variables == nil {
+			req.Variables = make(map[string]string)
+		}
+		req.Variables["model"] = req.Model
+	}
+
+	if len(req.ChatId) > 0 && len(req.FastgptChatId) == 0 {
+		req.FastgptChatId = req.ChatId
+	} else if len(req.ChatId) == 0 && len(req.FastgptChatId) > 0 {
+		req.ChatId = req.FastgptChatId
+	}
+}
+
+func humanSeePreChatVars(req *types.CompApiReq, apiKeyObj *ent.ApiKey, wf baseLogicWorkflow) {
+
+	fmt.Println("=========================================")
+	fmt.Printf("In ChatCompletion Get Token Info:\nKey:'%s'\n", apiKeyObj.Key)
+	fmt.Printf("Auth Token:'%s'\n", apiKeyObj.Key)
+	fmt.Printf("ApiKey AgentID:%d\n", apiKeyObj.AgentID)
+	fmt.Printf("ApiKey APIBase:'%s'\n", apiKeyObj.Edges.Agent.APIBase)
+	fmt.Printf("ApiKey APIKey:'%s'\n", apiKeyObj.Edges.Agent.APIKey)
+	fmt.Printf("ApiKey Type:%d\n", apiKeyObj.Edges.Agent.Type)
+	fmt.Printf("ApiKey Model:'%s'\n", apiKeyObj.Edges.Agent.Model)
+	fmt.Printf("EventType:'%s'\n", req.EventType)
+	fmt.Printf("req.ChatId:'%s VS req.FastgptChatId:'%s'\n", req.ChatId, req.FastgptChatId)
+	fmt.Println("=========================================")
+
+	switch wf.(type) {
+	case *MismatchChatLogic:
+		fmt.Println("MismatchChatLogic Flow.....")
+	case *IntentChatLogic:
+		fmt.Println("IntentChatLogic Flow.....")
+	case *FastgptChatLogic:
+		fmt.Println("FastgptChatLogic Flow.....")
+	default:
+		fmt.Println("Other Flow.....")
+	}
+}
+
+func humanSeeValidResult(ctx context.Context, req *types.CompApiReq, resp *types.CompOpenApiResp) {
+	clientFace, err := compapi.NewClient(ctx).GetClientActFace(req.EventType)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	taskData := ent.CompapiAsynctask{}
+	taskData.ID = 1234
+	taskData.ResponseChatItemID = req.ResponseChatItemId
+	taskData.EventType = req.EventType
+	taskData.ChatID = req.ChatId
+	taskData.ResponseRaw, err = resp.ToString()
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	var bs []byte
+	bs, err = clientFace.CallbackPrepare(&taskData)
+	if err != nil {
+		fmt.Println(err)
+	}
+	fmt.Printf("当前请求EventType:%s\n", req.EventType)
+	fmt.Printf("当前请求MODEL:%s\n", req.Model)
+	fmt.Println("client.CallbackPrepare结果[]byte版.........")
+	fmt.Println(string(bs))
+	nres := map[string]any{}
+	err = json.Unmarshal(bs, &nres)
+	if err != nil {
+		fmt.Println(err)
+	}
+	fmt.Println("client.CallbackPrepare结果map[string]any版.........")
+	fmt.Println(typekit.PrettyPrint(nres))
+
+	config := compapi.ResponseFormatConfig{}
+	if req.EventType == "mismatch" {
+		clientInst := clientFace.(*compapi.MismatchClient)
+		config = clientInst.ResponseFormatSetting(req)
+	} else if req.EventType == "intent" {
+		clientInst := clientFace.(*compapi.IntentClient)
+		config = clientInst.ResponseFormatSetting(req)
+	} else {
+		return
+	}
+	err = compapi.NewChatResult(resp).ParseContentAs(&config.ResformatStruct)
+	if err != nil {
+		fmt.Println(err)
+	}
+	nres["content"] = config.ResformatStruct
+	fmt.Println("client.CallbackPrepare结果ParseContentAs定制版.........")
+	fmt.Println(typekit.PrettyPrint(nres))
+}
+
 func apiKeyObjAdjust(eventType string, workId string, obj *ent.ApiKey) {
 	if eventType != "fastgpt" {
 		return
@@ -370,9 +398,9 @@ func apiKeyObjAdjust(eventType string, workId string, obj *ent.ApiKey) {
 var domainRegex = regexp.MustCompile(
 	// 多级域名(如 example.com)
 	`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$` +
-			`|` +
-			// 单级域名(如 localhost 或 mytest-svc)
-			`^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`,
+		`|` +
+		// 单级域名(如 localhost 或 mytest-svc)
+		`^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`,
 )
 
 func IsValidURL(input *string, adjust bool) bool {

+ 119 - 36
internal/utils/compapi/base.go

@@ -5,6 +5,8 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"reflect"
+	"sync"
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/contextkey"
 
@@ -56,21 +58,76 @@ func NewClient(ctx context.Context, opts ...ClientOption) *Client {
 	return &client
 }
 
-func (me *Client) getClientActFace(clientType string) (clientActionFace, error) {
-	var (
-		err     error
-		actFace clientActionFace
-	)
+// 以下新增加client类型工厂自注册相关函数
+// --- Registry Func Type---
+type ClientBuilderFunc func(c *Client) (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}}
+var (
+	clientRegistry       = make(map[string]ClientBuilderFunc)
+	defaultClientType    string
+	defaultClientBuilder ClientBuilderFunc
+	registryMutex        sync.RWMutex // Protects both registry and default builder
+)
+
+// RegisterClient remains the same
+func RegisterClient(clientType string, builder ClientBuilderFunc) error {
+	registryMutex.Lock()
+	defer registryMutex.Unlock()
+
+	if _, exists := clientRegistry[clientType]; exists {
+		return fmt.Errorf("client type '%s' already registered", clientType)
 	}
-	return actFace, err
+	clientRegistry[clientType] = builder
+	return nil
+}
+
+// RegisterDefaultClient registers a builder function as the default fallback.
+// Typically called from the init() function of the default client implementation.
+func RegisterDefaultClient(clientType string, builder ClientBuilderFunc) error {
+	registryMutex.Lock()
+	defer registryMutex.Unlock()
+
+	if defaultClientBuilder != nil {
+		// Prevent multiple defaults or decide on override behavior
+		return fmt.Errorf("default client type '%s' already registered, cannot register '%s' as default", defaultClientType, clientType)
+	}
+	defaultClientBuilder = builder
+	defaultClientType = clientType // Store the name
+	return nil
+}
+
+// GetClientBuilder remains the same
+func GetClientBuilder(clientType string) (ClientBuilderFunc, bool) {
+	registryMutex.RLock()
+	defer registryMutex.RUnlock()
+	builder, exists := clientRegistry[clientType]
+	return builder, exists
+}
+
+// GetDefaultClientBuilder retrieves the registered default builder.
+func GetDefaultClientBuilder() (ClientBuilderFunc, bool) {
+	registryMutex.RLock()
+	defer registryMutex.RUnlock()
+	exists := defaultClientBuilder != nil
+	return defaultClientBuilder, exists
+}
+
+// 根据client生成不同实现了clientActionFace接口的client
+func (me *Client) GetClientActFace(clientType string) (clientActionFace, error) {
+	// 1. Try to find the specific client type
+	builder, exists := GetClientBuilder(clientType)
+	if exists {
+		return builder(me)
+	}
+
+	// 2. If not found, try to get the default client builder
+	defaultBuilder, defaultExists := GetDefaultClientBuilder()
+	if defaultExists {
+		return defaultBuilder(me)
+	}
+
+	// 3. If neither specific nor default exists, return an error
+	return nil, fmt.Errorf("unsupported client type '%s' and no default client registered", clientType)
 }
 
 func (me *Client) NewOAC() {
@@ -86,20 +143,14 @@ func (me *Client) NewOAC() {
 }
 
 func (me *Client) Callback(clientType string, callbackUrl string, params any) (map[string]any, error) {
-	actFace, err := me.getClientActFace(clientType)
+	actFace, err := me.GetClientActFace(clientType)
 	if err != nil {
 		return nil, err
 	}
-	/*
-		switch actFace.(type) {
-		case *MismatchClient:
-			fmt.Printf("MismatchClient.Callback() for EventType:'%s'\n", clientType)
-		case *FastgptClient:
-			fmt.Printf("FastgptClient.Callback() for EventType:'%s'\n", clientType)
-		default:
-			fmt.Printf("maybe StdClient.Callback() for EventType:'%s'\n", clientType)
-		}
-	*/
+
+	//临时测试
+	//humanSeeActFaceMake(actFace, clientType, "Callback")
+
 	var newParams []byte
 	if newParams, err = actFace.CallbackPrepare(params); err != nil {
 		return nil, err
@@ -120,20 +171,13 @@ func (me *Client) Chat(chatInfo *types.CompApiReq) (*types.CompOpenApiResp, erro
 		actFace clientActionFace
 		apiResp *types.CompOpenApiResp
 	)
-	actFace, err = me.getClientActFace(chatInfo.EventType)
+	actFace, err = me.GetClientActFace(chatInfo.EventType)
 	if err != nil {
 		return nil, err
 	}
-	/*
-		switch actFace.(type) {
-		case *MismatchClient:
-			fmt.Printf("MismatchClient.Chat() for EventType:'%s'\n", chatInfo.EventType)
-		case *FastgptClient:
-			fmt.Printf("FastgptClient.Chat() for EventType:'%s'\n", chatInfo.EventType)
-		default:
-			fmt.Printf("maybe StdClient.Chat() for EventType:'%s'\n", chatInfo.EventType)
-		}
-	*/
+
+	//临时测试
+	//humanSeeActFaceMake(actFace, chatInfo.EventType, "Chat")
 
 	err = actFace.BuildRequest(chatInfo)
 	if err != nil {
@@ -147,7 +191,7 @@ func (me *Client) Chat(chatInfo *types.CompApiReq) (*types.CompOpenApiResp, erro
 	return apiResp, err
 }
 
-func GenerateSchema[T any]() interface{} {
+func GenerateSchema[T any]() any {
 	// Structured Outputs uses a subset of JSON schema
 	// These flags are necessary to comply with the subset
 	reflector := jsonschema.Reflector{
@@ -159,6 +203,30 @@ func GenerateSchema[T any]() interface{} {
 	return schema
 }
 
+func GenerateSchemaFromValue(value any) (any, error) {
+	if value == nil {
+		// 处理 nil 值的情况,根据你的需求决定是返回错误还是空 schema
+		return nil, fmt.Errorf("cannot generate schema from a nil value")
+	}
+	reflector := jsonschema.Reflector{
+		AllowAdditionalProperties: false, // 根据你的需求设置
+		DoNotReference:            true,  // 根据你的需求设置
+	}
+	// 直接对传入的值进行反射
+	schema := reflector.Reflect(value)
+	return schema, nil
+}
+
+// 新函数:通过 reflect.Type 生成 Schema
+func GenerateSchemaByType(t reflect.Type) any {
+	reflector := jsonschema.Reflector{
+		AllowAdditionalProperties: false,
+		DoNotReference:            true,
+	}
+	schema := reflector.ReflectFromType(t)
+	return schema
+}
+
 func getHttpResponseTools(ctx context.Context) (*http.ResponseWriter, *http.Flusher, error) {
 	hw, ok := contextkey.HttpResponseWriterKey.GetValue(ctx) //context取出http.ResponseWriter
 	if !ok {
@@ -198,3 +266,18 @@ func streamOut(ctx context.Context, res *http.Response) {
 
 	httpx.Ok((*hw))
 }
+
+func humanSeeActFaceMake(actFace clientActionFace, clientType string, funcName string) {
+	clientName := ""
+	switch actFace.(type) {
+	case *MismatchClient:
+		clientName = "MismatchClient"
+	case *IntentClient:
+		clientName = "IntentClient"
+	case *FastgptClient:
+		clientName = "FastgptClient"
+	default:
+		clientName = "maybe StdClient"
+	}
+	fmt.Printf("%s.%s() for EventType:'%s'\n", clientName, funcName, clientType)
+}

+ 26 - 0
internal/utils/compapi/fastgpt.go

@@ -1,5 +1,31 @@
 package compapi
 
+/*
+	目前看FastgptClient=StdClient,相当于别名
+	以后再分或合并再议
+*/
+
 type FastgptClient struct {
 	StdClient
 }
+
+// 向Client.getClientActFace工厂方法注册(包含默认)
+func init() {
+	// Define the builder once
+	builder := func(c *Client) (clientActionFace, error) {
+		return &FastgptClient{StdClient: StdClient{Client: c}}, nil
+	}
+
+	// Register it under its specific name "fastgpt"
+	err := RegisterClient("fastgpt", builder)
+	if err != nil {
+		//panic(fmt.Sprintf("Failed to register client type 'fastgpt': %v", err))
+	}
+
+	// **** Also register it as the DEFAULT client ****
+	err = RegisterDefaultClient("fastgpt", builder)
+	if err != nil {
+		// This might happen if another client also tried to register as default
+		//panic(fmt.Sprintf("Failed to register 'fastgpt' as default client: %v", err))
+	}
+}

+ 79 - 0
internal/utils/compapi/intent.go

@@ -0,0 +1,79 @@
+package compapi
+
+import (
+	"wechat-api/internal/types"
+)
+
+type IntentClient struct {
+	MismatchClient
+}
+
+func (me *IntentClient) ResponseFormatSetting(req *types.CompApiReq) ResponseFormatConfig {
+
+	//Message重构的配置
+	me.ResformatConfig.SysmesArgs = []any{}
+	me.ResformatConfig.SysmesTmpl = `# 任务
+1. 首先,判断用户的第一句话是否说了:“你好,(任意内容)通话”,如果说了,则不用理会评级规则,直接强制分配为"语音助手"
+2. 如果不属于“语音助手”,请根据评级规则,对聊天记录给出评级、置信度、评分依据(逐项分析不要遗漏)
+
+# 细节说明
+置信度从0到1,0为置信度最低,1为置信度最高。`
+
+	me.ResformatConfig.UsermesArgs = []any{req.Variables["chat_history"]}
+	me.ResformatConfig.UsermesTmpl = `# 评级规则:
+        [
+        {
+            'rule': "用户表现出对产品有积极回应,询问有关产品的问题,或愿意进行后续操作,如确认微信号、询问发货地址或表达接受产品(例:'行'、'好'、'可以'、'是的')。",
+            'score': '有意向'},
+        {
+            'rule': "用户虽然未积极回应或表示不需要,但却有相关需求、或着符合某项购买条件、又或着询问了机器人问题,即便后期疲软或持否定态度,可移交销售顾问进行进一步的需求分析,以便改善产品或定制符合客户要求的产品",
+            'score': '待进一步分析'},
+        {
+            'rule': "用户表示自己在忙、在开会等,现在不太方便沟通。又或者对电话的真实性存疑,怀疑是诈骗电话,可后续让专业客服继续跟进",
+            'score': '暂时无法沟通'},
+        {
+            'rule': "除上述规则外的其他用户,包括:用户未回复直接挂机、用户拒绝后直接挂机的情况。请注意客户资源珍贵,请尽量归类为'有意向'或'待进一步分析',后续再由销售专家定性。",
+            'score': '其他'}
+    ]
+
+        # 聊天记录
+        %s
+        `
+
+	//ResponseFormat设置的配置
+	me.ResformatConfig.ResformatDesc = "为通话记录进行评级"
+	me.ResformatConfig.ResformatTxt = `{
+    "score": str, #评分结果:有意向、待进一步分析、暂时无法沟通、其他
+    "confidence_score": int, #置信度分数,范围从0.0到1.0
+    "scoring_criteria": str, #请逐步介绍为何评为这个结果
+}`
+	me.ResformatConfig.ResformatStruct = struct {
+		Score           string  `json:"score" jsonschema_description:"评分结果:有意向、待进一步分析、暂时无法沟通、其他"`
+		ConfidenceScore float32 `json:"confidence_score" jsonschema_description:"置信度分数,范围从0.0到1.0"`
+		ScoringCriteria string  `json:"scoring_criteria" jsonschema_description:"请逐步介绍为何评为这个结果"`
+	}{}
+	me.ResformatConfig.HaveSet = true
+
+	return me.ResformatConfig
+}
+
+func (me *IntentClient) BuildRequest(req *types.CompApiReq) error {
+
+	//设置相关个性化ResponseFormat配置信息
+	if !me.ResformatConfig.HaveSet {
+		me.ResponseFormatSetting(req)
+	}
+
+	return me.MismatchClient.BuildRequest(req)
+}
+
+// 向Client.getClientActFace工厂方法注册
+func init() {
+	builder := func(c *Client) (clientActionFace, error) {
+		return &IntentClient{MismatchClient{StdClient: StdClient{Client: c}}}, nil
+	}
+	err := RegisterClient("intent", builder)
+	if err != nil {
+		//panic(fmt.Sprintf("Failed to register client type 'mismatch': %v", err))
+	}
+}

+ 140 - 45
internal/utils/compapi/mismatch.go

@@ -11,78 +11,173 @@ import (
 
 type MismatchClient struct {
 	StdClient
+	ResformatConfig ResponseFormatConfig
 }
 
-// Generate the JSON schema at initialization time
-var MismatchResponseSchema = GenerateSchema[MismatchResponse]()
+func (me *MismatchClient) ResponseFormatSetting(req *types.CompApiReq) ResponseFormatConfig {
 
-type MismatchResponse struct {
-	UserIntent   string   `json:"user_intent" jsonschema_description:"用户意图"`
-	SimilarReply []string `json:"similar_reply" jsonschema_description:"类似回复"`
-	Keywords     []string `json:"keywords" jsonschema_description:"关键词库"`
-	Regular      []string `json:"regular" 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 *MismatchClient) BuildRequest(req *types.CompApiReq) error {
-
-	//先重构message
-	newMessSlice := make([]types.StdCompMessage, 2)
-	newMessSlice[0] = types.StdCompMessage{Role: "system", Content: ` # 任务介绍
+	//Message重构的配置
+	me.ResformatConfig.SysmesArgs = []any{req.Variables["missed"]}
+	me.ResformatConfig.SysmesTmpl = ` # 任务介绍
 		公司在用程序巡检电话通话记录时,有些用户的回复内容没有被关键词库识别到,请根据以下通话记录,给出一些类似的回复,以及对关键词库的具体建议,非常感谢!
 
 		# 输出要求
 		1. 用户意图:结合上下文,首先考虑可能的语音识别错误并纠错,然后分析未识别内容的根本意图,用户说这句话的背后真实目的是什么,结合上下文挖掘用户最深的意图,请不要停留在表面的意思。
-		2. 类似回复:首先应包含未识别的内容原文:` + req.Variables["missed"] + `。其次生成尽可能多的类似意图的回复。
+		2. 类似回复:首先应包含未识别的内容原文:%s。其次生成尽可能多的类似意图的回复。
 		2. 关键词库:从类似回复中抽取特征词,要求有两点:一是特征词应尽可能的准确,即要覆盖住此类问题,又要尽可能避免和其他语境相冲突。二是需要注重通用性,一些专有名词、人名、地名、产品名、公司名等需要排除在外。
 		3. 正则表达式:对关键词词进行解耦,用正则表达式进行表示,专注于主要内容,排除次要内容
 
 		# 注意事项
 		1. 应贴近真实的外呼场景,用户的素质参差不齐,请避免使用任何浮夸的用词,避免使用高级词汇,避免使用任何的礼貌用语或敬语,适当的低素质些,请至少给出20条结果
 		2. 通话记录为录音转译,因此可能有错别字或音同字不同的情况(例如:借和接),请根据上下文分析后就成可能的错字错误
-		3. 正则关键词库和正则表达式中也应该考虑到音同字不同的情况,且避免使用匹配次数相关的语法如{0,2}`}
+		3. 正则关键词库和正则表达式中也应该考虑到音同字不同的情况,且避免使用匹配次数相关的语法如{0,2}`
 
-	newMessSlice[1] = types.StdCompMessage{Role: "user", Content: `
-	# 通话记录` + req.Variables["chat_history"] + `
+	me.ResformatConfig.UsermesArgs = []any{req.Variables["chat_history"], req.Variables["missed"]}
+	me.ResformatConfig.UsermesTmpl = `
+	# 通话记录%s
 
-	# 可能识别有误的内容:` + req.Variables["missed"]}
+	# 可能识别有误的内容:%s`
 
-	//再构造ResponseFormat
-	if !IsOpenaiModel(req.Model) {
-		newMessSlice[1].Content = newMessSlice[1].Content.(string) + `{
+	//ResponseFormat设置的配置
+	me.ResformatConfig.ResformatDesc = "从通话记录中提取表单"
+	me.ResformatConfig.ResformatTxt = `{
     "user_intent": str, #用户意图
     "similar_reply": list[str], #类似回复
     "keywords": list[str], #关键词库
     "regular": list[str], #正则表达式
 }`
-		req.ResponseFormat = openai.ResponseFormatJSONObjectParam{Type: "json_object"}
-	} else {
-		schemaParam := openai.ResponseFormatJSONSchemaJSONSchemaParam{
-			Name:        "keyword_schema",
-			Description: openai.String("从通话记录中提取表单"),
-			Schema:      MismatchResponseSchema,
-			Strict:      openai.Bool(true),
+	me.ResformatConfig.ResformatStruct = struct {
+		UserIntent   string   `json:"user_intent" jsonschema_description:"用户意图"`
+		SimilarReply []string `json:"similar_reply" jsonschema_description:"类似回复"`
+		Keywords     []string `json:"keywords" jsonschema_description:"关键词库"`
+		Regular      []string `json:"regular" jsonschema_description:"正则表达式"`
+	}{}
+	me.ResformatConfig.HaveSet = true
+
+	return me.ResformatConfig
+}
+
+// 向Client.getClientActFace工厂方法注册
+func init() {
+	builder := func(c *Client) (clientActionFace, error) {
+		return &MismatchClient{StdClient: StdClient{Client: c}}, nil
+	}
+	err := RegisterClient("mismatch", builder)
+	if err != nil {
+		//panic(fmt.Sprintf("Failed to register client type 'mismatch': %v", err))
+	}
+}
+
+type ResponseFormatConfig struct {
+	SysmesTmpl  string `json:"sysmes_tmpl" jsonschema_description:"系统消息文本模板"`
+	SysmesArgs  []any  `json:"sysmes_args" jsonschema_description:"系统消息文本参数列表"`
+	UsermesTmpl string `json:"usermes_tmpl" jsonschema_description:"用户消息文本模板"`
+	UsermesArgs []any  `json:"usermes_args" jsonschema_description:"用户消息文本参数列表"`
+
+	ResformatDesc   string `json:"resformat_desc" jsonschema_description:"response_format的介绍描述"`
+	ResformatTxt    string `json:"resformat_txt" jsonschema_description:"response_format文本版本"`
+	ResformatStruct any    `json:"resformat_struct" jsonschema_description:"response_format结构体版本"`
+	HaveSet         bool
+}
+
+type StringGen func() string
+
+func CreateStringGen(format string, args ...interface{}) StringGen {
+	return func() string {
+		return fmt.Sprintf(format, args...)
+	}
+}
+
+/*
+
+{"response_format":
+	{"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"},
+				"user_intent":{"description":"用户意图","type":"string"}
+			    },
+		     "required":
+				["user_intent","similar_reply","keywords","regular"],
+		      "type":"object"
+		    },
+		"strict":true
+	   }
+	}
+}
+
+{"response_format":
+	{"type":"json_object"}
+	}
+*/
+
+func (me *MismatchClient) RebuildMessage(openaiModel bool) []types.StdCompMessage {
+	newMessSlice := make([]types.StdCompMessage, 2)
+	newMessSlice[0] = types.StdCompMessage{Role: "system",
+		Content: CreateStringGen(me.ResformatConfig.SysmesTmpl, me.ResformatConfig.SysmesArgs...)()}
+	resFormatStr := ""
+	if !openaiModel {
+		resFormatStr = CreateStringGen(me.ResformatConfig.ResformatTxt)()
+	}
+	newMessSlice[1] = types.StdCompMessage{Role: "user",
+		Content: CreateStringGen(me.ResformatConfig.UsermesTmpl, me.ResformatConfig.UsermesArgs...)() + resFormatStr}
+
+	return newMessSlice
+}
+
+func (me *MismatchClient) BuildJsonSchemaParam() (*openai.ResponseFormatJSONSchemaJSONSchemaParam, error) {
+	schema, err := GenerateSchemaFromValue(me.ResformatConfig.ResformatStruct)
+	if err != nil {
+		return nil, err
+	}
+	schemaParam := openai.ResponseFormatJSONSchemaJSONSchemaParam{
+		Name:        "keyword_schema",
+		Description: openai.String(me.ResformatConfig.ResformatDesc),
+		Schema:      schema,
+		Strict:      openai.Bool(true),
+	}
+	return &schemaParam, nil
+}
+
+func (me *MismatchClient) GetResFormat(openaiModel bool) (any, error) {
+	if openaiModel { //针对openai兼容api 设置JsonSchema 定制化输出结构
+		schemaParam, err := me.BuildJsonSchemaParam()
+		if err != nil {
+			return nil, err
 		}
-		req.ResponseFormat = openai.ResponseFormatJSONSchemaParam{JSONSchema: schemaParam}
+		return &openai.ResponseFormatJSONSchemaParam{JSONSchema: *schemaParam}, nil
+	} else { //针对不兼容比如Deepseek系设置JsonObject
+		return &openai.ResponseFormatJSONObjectParam{Type: "json_object"}, nil
+	}
+}
+
+func (me *MismatchClient) BuildRequest(req *types.CompApiReq) error {
+
+	//设置相关个性化ResponseFormat配置信息
+	if !me.ResformatConfig.HaveSet {
+		me.ResponseFormatSetting(req)
 	}
-	//req.Model = oldModel
-	req.Messages = newMessSlice
+	openaiModel := IsOpenaiModel(req.Model)
+	//重构req.Message
+	req.Messages = me.RebuildMessage(openaiModel)
+	//设置req.ResponseFormat
+	var err error
+	req.ResponseFormat, err = me.GetResFormat(openaiModel)
 
-	return nil
+	return err
 }
 
 func (me *MismatchClient) CallbackPrepare(params any) ([]byte, error) {
+
 	taskData, ok := params.(*ent.CompapiAsynctask)
 	if !ok {
 		return nil, errors.New("invalid callback taskdata")

+ 3 - 3
sql/wechat/compapi_asynctask.sql

@@ -4,7 +4,7 @@
 -- https://tableplus.com/
 --
 -- Database: wechat
--- Generation Time: 2025-04-16 15:43:45.0380
+-- Generation Time: 2025-04-26 20:01:55.4800
 -- -------------------------------------------------------------
 
 
@@ -38,8 +38,8 @@ CREATE TABLE `compapi_asynctask` (
   `retry_count` tinyint DEFAULT '0' COMMENT 'retry count | 重试次数',
   `last_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '最后一次错误信息',
   PRIMARY KEY (`id`),
-  KEY `task_status` (`task_status`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=873 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+  KEY `idx_compapi_task_status_chat_id_id_desc` (`task_status`,`chat_id`,`id` DESC)
+) ENGINE=InnoDB AUTO_INCREMENT=1005 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;