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) }