126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package eino
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/cloudwego/eino-ext/components/model/qwen"
|
|
"github.com/cloudwego/eino/components/prompt"
|
|
"github.com/cloudwego/eino/schema"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/glog"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
var globalChatModel *qwen.ChatModel
|
|
|
|
func init() {
|
|
ctx := context.Background()
|
|
|
|
apiKey := g.Cfg().MustGet(ctx, "eino.chatmodel.apiKey").String()
|
|
model := g.Cfg().MustGet(ctx, "eino.chatmodel.model").String()
|
|
|
|
var err error
|
|
globalChatModel, err = qwen.NewChatModel(ctx, &qwen.ChatModelConfig{
|
|
APIKey: apiKey,
|
|
Model: model,
|
|
BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
Temperature: gconv.PtrFloat32(0.7), // 客服最佳
|
|
MaxTokens: gconv.PtrInt(1024), // 最长回答
|
|
TopP: gconv.PtrFloat32(1.0),
|
|
})
|
|
if err != nil {
|
|
glog.Errorf(ctx, "初始化大模型失败: %v", err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// NewChatModel 只处理逻辑,不复用创建模型
|
|
func NewChatModel(ctx context.Context, content string, docs []*schema.Document) (replyMsg *schema.Message, sources []string, err error) {
|
|
// 1. 构建参考知识
|
|
knowledge, sources := buildKnowledgeAndSources(docs)
|
|
|
|
// 2. 构建提示词
|
|
msgs, err := buildPromptMessages(ctx, knowledge, content)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// 3. 🔥 直接使用全局单例,不重复创建
|
|
replyMsg, err = streamGenerateAnswer(ctx, globalChatModel, msgs)
|
|
|
|
return
|
|
}
|
|
|
|
// buildKnowledgeAndSources 拼接参考知识 + 提取文档来源
|
|
func buildKnowledgeAndSources(docs []*schema.Document) (string, []string) {
|
|
var knowledge string
|
|
var sources []string
|
|
|
|
for i, doc := range docs {
|
|
knowledge += fmt.Sprintf("[参考%d] %s\n", i+1, doc.Content)
|
|
|
|
// 提取 document_id
|
|
if docID, ok := doc.MetaData["document_id"].(int64); ok && docID > 0 {
|
|
sources = append(sources, gconv.String(docID))
|
|
}
|
|
}
|
|
return knowledge, sources
|
|
}
|
|
|
|
// buildPromptMessages 构建提示词模板
|
|
func buildPromptMessages(ctx context.Context, knowledge string, question string) (msgs []*schema.Message, err error) {
|
|
promptTpl := prompt.FromMessages(
|
|
schema.FString,
|
|
&schema.Message{
|
|
Role: schema.System,
|
|
// Content: `你是专业的客服助手,语气友好。
|
|
//如果参考知识中有相关信息,请优先依据参考知识回答。
|
|
//如果没有相关信息,就正常回答,不要说无法回答。
|
|
//
|
|
//参考知识:
|
|
//{knowledge}`,
|
|
Content: `你是专业的客服助手,语气友好。
|
|
请根据参考知识回答用户问题,无法回答则说:抱歉,我暂时无法回答这个问题。
|
|
|
|
参考知识:
|
|
{knowledge}`,
|
|
},
|
|
&schema.Message{
|
|
Role: schema.User,
|
|
Content: "{question}",
|
|
},
|
|
)
|
|
|
|
return promptTpl.Format(ctx, map[string]any{
|
|
"knowledge": knowledge,
|
|
"question": question,
|
|
})
|
|
}
|
|
|
|
// streamGenerateAnswer 流式生成
|
|
func streamGenerateAnswer(ctx context.Context, chatModel *qwen.ChatModel, msgs []*schema.Message) (reply *schema.Message, err error) {
|
|
|
|
sr, err := chatModel.Stream(ctx, msgs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("stream failed: %w", err)
|
|
}
|
|
|
|
var chunks []*schema.Message
|
|
for {
|
|
chunk, err := sr.Recv()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("stream recv failed: %w", err)
|
|
}
|
|
chunks = append(chunks, chunk)
|
|
}
|
|
|
|
return schema.ConcatMessages(chunks)
|
|
}
|