Explorar o código

Merge branch 'develop/apikey_and_agent_v1' into debug

* develop/apikey_and_agent_v1:
  增加对非openai平台JSON格式的解析
  响应新需求修改
  compapi重大几处修改 1.API访问验权和获得用户信息大调整,api_key表与agent表关联,使用其中的api_base/api_key/model作为后续请求大模型的权限依据。并且支持python接口的并入 2.utils/compapi重构大调整,可以方便容纳不同event_type的个性化需求。相应的api前端logic及异步操作相关代码都做了修改。 3.移植了python泛化接口/call_center/mismatch/analyze
  增加一个建表结构SQL语句
  1.全流程去掉workId 2.解决访问fastgpt api出错但err依然为nil的问题,通过api返回值结构增加新成员来捕获 3.API请求的异步模式全流程实现(下任务及cron中并发处理
  删除ent
  接口的异步任务受理(包括相关ent及logic内的处理)

# Conflicts:
#	crontask/init.go
#	ent/client.go
#	ent/ent.go
#	ent/mutation.go
#	go.mod
#	internal/logic/chat/chat_completions_logic.go
#	internal/utils/compapi/func.go
#	internal/utils/compapi/mismatch.go
#	internal/utils/compapi/result.go
boweniac hai 2 semanas
pai
achega
25d89f86e1

+ 6 - 0
go.mod

@@ -48,6 +48,12 @@ require github.com/invopop/jsonschema v0.13.0
 require github.com/google/uuid v1.6.0
 
 require (
+	github.com/invopop/jsonschema v0.13.0
+	github.com/openai/openai-go v0.1.0-beta.9
+//github.com/openai/openai-go v0.1.0-alpha.62
+)
+
+require (
 	ariga.io/atlas v0.19.2 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
 	github.com/ArtisanCloud/PowerLibs/v2 v2.0.49 // indirect

+ 8 - 1
internal/logic/chat/chat_completions_logic.go

@@ -164,9 +164,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)
-	return compapi.NewClient(l.ctx, compapi.WithApiBase(apiKeyObj.Edges.Agent.APIBase),
+	resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase(apiKeyObj.Edges.Agent.APIBase),
 		compapi.WithApiKey(apiKeyObj.Edges.Agent.APIKey)).
 		Chat(req)
+	/*
+			res := compapi.MismatchResponse{}
+		err = compapi.NewChatResult(resp).ParseContentAs(&res)
+		fmt.Println(err)
+		fmt.Println(typekit.PrettyPrint(res))
+	*/
+	return resp, err
 }
 
 func (l *ChatCompletionsLogic) AppendAsyncRequest(apiKeyObj *ent.ApiKey, req *types.CompApiReq) error {

+ 14 - 0
internal/utils/compapi/func.go

@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/contextkey"
@@ -16,6 +17,19 @@ import (
 	"github.com/zeromicro/go-zero/rest/httpx"
 )
 
+func IsOpenaiModel(model string) bool {
+
+	prefixes := []string{"gpt-4", "gpt-3", "o1", "o3"}
+
+	// 遍历所有前缀进行检查
+	for _, prefix := range prefixes {
+		if strings.HasPrefix(model, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
 type StdChatClient struct {
 	*openai.Client
 }

+ 2 - 4
internal/utils/compapi/mismatch.go

@@ -55,11 +55,9 @@ func (me *MismatchClient) BuildRequest(req *types.CompApiReq) error {
 
 	# 可能识别有误的内容:` + req.Variables["missed"]}
 
-	//oldModel := req.Model
-	//req.Model = "deepseek-v3"
 	//再构造ResponseFormat
-	if req.Model == "deepseek-v3" || req.Model == "deepseek-chat" {
-		newMessSlice[1].Content = newMessSlice[1].Content.(string) + `json:{
+	if !IsOpenaiModel(req.Model) {
+		newMessSlice[1].Content = newMessSlice[1].Content.(string) + `{
     "user_intent": str, #用户意图
     "similar_reply": list[str], #类似回复
     "keywords": list[str], #关键词库

+ 54 - 6
internal/utils/compapi/result.go

@@ -51,16 +51,19 @@ func (r *ChatResult) ParseContentAs(target any) error {
 		return errors.New("parseContent err: content is empty or unavailable")
 	}
 
-	// (可选)清理可能的 Markdown ```json ``` 包装
-	if strings.HasPrefix(content, "```json") && strings.HasSuffix(content, "```") {
-		content = strings.TrimSuffix(strings.TrimPrefix(content, "```json"), "```")
-		content = strings.TrimSpace(content)
+	if !IsOpenaiModel(r.Model) { //不支持Response Schema的要特殊处理一下
+		var isJsonContent bool
+		content, isJsonContent = ExtractJSONContent(content)
+		if !isJsonContent {
+			return errors.New("invalid json content")
+		}
 	}
-
 	err = json.Unmarshal([]byte(content), target)
 	if err != nil {
-		return fmt.Errorf("parseContent err: failed to unmarshal content JSON into target type '%w'", err)
+		return fmt.Errorf("parseContent err: failed to unmarshal content JSON "+
+			"into target type '%w'", err)
 	}
+
 	return nil
 }
 
@@ -158,3 +161,48 @@ func WrapJSON(input any, warpKey string, checkValid bool) ([]byte, error) {
 	}
 	return outputBytes, nil
 }
+
+func ParseContentAs(content string, target any) error {
+	// 清理可能的 Markdown ```json ``` 包装
+	if strings.HasPrefix(content, "```json") && strings.HasSuffix(content, "```") {
+		content = strings.TrimSuffix(strings.TrimPrefix(content, "```json"), "```")
+		content = strings.TrimSpace(content)
+	}
+
+	if err := json.Unmarshal([]byte(content), target); err != nil {
+		return fmt.Errorf("parseContent err:failed to unmarshal"+
+			" content JSON into target type '%w'", err)
+	}
+	return nil
+}
+
+func ExtractJSONContent(s string) (string, bool) {
+	startMarker := "```json"
+	endMarker := "```"
+
+	// 寻找起始标记
+	startIdx := strings.Index(s, startMarker)
+	if startIdx == -1 {
+		return "", false // 没有起始标记
+	}
+
+	// 寻找结束标记(需在起始标记之后查找)
+	endIdx := strings.LastIndex(s, endMarker)
+	if endIdx == -1 || endIdx <= startIdx {
+		return "", false // 没有结束标记或标记顺序错误
+	}
+
+	// 计算内容范围
+	contentStart := startIdx + len(startMarker)
+	contentEnd := endIdx
+
+	// 提取内容并去除前后空白
+	content := strings.TrimSpace(s[contentStart:contentEnd])
+
+	// 若内容为空视为无效
+	if content == "" {
+		return "", false
+	}
+
+	return content, true
+}