返回顶部
热门问答 更多热门问答
技术文章 更多技术文章

一文讲透:大模型应用开发中的多轮对话实战案例

[复制链接]
链载Ai 显示全部楼层 发表于 3 小时前 |阅读模式 打印 上一主题 下一主题

多轮对话技术是人工智能领域的重要研究方向,很多公司都有这样的场景,其目标是构建能够理解上下文、识别意图并生成连贯响应的智能系统。今天我来通过一个真实案例并给出核心代码给你讲透,请一定看完。


一、实际落地场景中的问题


多轮对话在业务场景落地的过程中有一系列问题:

  • 业务场景的复杂性
多轮对话系统需处理用户连续提问、话题切换、信息省略等复杂交互,例如用户从“订机票”突然转向“支付方式”,系统需动态调整任务目标。传统基于规则或检索的方法难以应对此类动态场景。
  • 模型能力的局限性
尽管LLM在单轮对话中表现优异,但其对长上下文的记忆能力有限,且生成内容易出现逻辑矛盾(如前文提及“北京”后文误答“上海天气”)。此外,模型对模糊表达(如“大概几点”)的意图识别准确率仍需提升。
  • 工程实现的复杂性
多轮对话系统需集成内容审核、用户意图识别、提示词构建、历史消息、模型调用、中断处理、存储层实现等模块,并解决实时性、成本控制等问题。

二、实现的核心流程

从工程的角度来说,实现多轮对话系统的核心需要从这几个方面进行:

  • 上下文控制:设置超时时间,并在适当的地方进行取消操作。
  • 初始化请求管道:创建模型响应通道和中断监听通道。
  • 前置处理:
    • 创建初始消息记录
    • 对用户问题进行安全审核
    • 中断监听:创建一个协程监听用户的中断请求。
    • 记忆网络:存储并索引历史对话。
    • 构建模型提示词:根据请求内容构建AI模型需要的提示词。
    • 调用模型获取回答:异步调用AI模型,将响应发送到输出通道。
    • 处理模型响应:
      • 使用select监听多个通道
      • 处理超时情况
      • 处理模型返回的数据包
      • 处理用户中断请求
    • 结束逻辑:
      • 更新最终消息内容和状态
      • 记录会话历史
      • 设置输出信息

    三、代码实现


    以下是基于Golang的Kratos框架和SSE推送技术的实践。


    核心处理流程:

      func(f*FreeAskUsecase)freeAskHandle(ctxcontext.Context,req*FreeAskReq,output*FreeAskResp){//上下文控制ctx,cancel:=context.WithTimeout(ctx,time.Minute*2)defercancel()//初始化请求管道modelCh:=make(chan*ModelResponse,10)interruptCh:=make(chanstruct{},1)varmsgIdStrstringvarmsg*Message//前置处理:创建消息、安全审核等msg,err:=f.createInitialMessage(ctx,req)iferr!=nil{f.log.Errorf("创建初始消息失败:%v",err)output.Status=StatusFailedoutput.Message="创建消息失败"return}msgIdStr=msg.Msg.Idoutput.MsgId=msgIdStr//获取历史对话记录chatHistory,err:=f.repo.GetRecentChatHistory(ctx,req.TalId,req.SubjectId,2)iferr!=nil{f.log.Warnf("获取历史对话记录失败:%v",err)//继续处理,不影响主流程}//构建消息历史messages:=make([]Message,0,len(chatHistory)*2+1)//系统提示词systemPrompt:=f.buildSystemPrompt(req.SubjectId,intent,intentDetails)messages=append(messages,Message{Role:"system",//系统角色Content:systemPrompt,})//添加历史对话for_,chat:=rangechatHistory{//用户问题messages=append(messages,Message{Role:"user",//用户角色Content:chat.Question,})//AI回答messages=append(messages,Message{Role:"assistant",//模型角色Content:chat.Answer,})}//添加当前问题messages=append(messages,Message{Role:"user",Content:req.Question,})//用户意图识别intent,intentDetails:=f.recognizeUserIntent(ctx,req.Question)f.log.Infof("用户意图识别结果:%s,详情:%+v",intent,intentDetails)//根据意图处理特殊请求iff.handleSpecialIntent(ctx,intent,intentDetails,req,output){//如果特殊意图已处理完毕,直接返回return}//安全审核safeResult,err:=f.safetyCheck(ctx,req.Question)iferr!=nil||!safeResult.IsSafe{f.log.Errorf("内容安全审核未通过:%v",err)output.Status=StatusRejectedoutput.Message="内容包含不安全信息,请修改后重试"//更新消息状态为拒绝f.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusRejected)return}//2.创建中断监听//用户可能会打断模型输出gof.listenForInterruption(ctx,req.TalId,msgIdStr,interruptCh)//3.构建模型提示词//将用户意图信息添加到提示词中promptOptions:=&romptOptions{Intent:intent,IntentDetails:intentDetails,}prompt,err:=f.buildPromptWithOptions(ctx,req,promptOptions)iferr!=nil{f.log.Errorf("构建提示词失败:%v",err)output.Status=StatusFailedoutput.Message="系统处理异常"return}//创建DeepSeek-R1模型请求modelRequest:=&DeepSeekModelRequest{Model:"deepseek-r1",Messages:messages,MaxTokens:2048,Temperature:0.7,Stream:true,//流式输出}//构建模型上下文modelCtx,modelCancel:=context.WithCancel(ctx)defermodelCancel()//添加中断处理gofunc(){select{case<-interruptCh://接收到中断信号,取消模型请求modelCancel()case<-ctx.Done()://上下文已结束return}}()//创建响应通道modelCh:=make(chan*DeepSeekResponse,10)//异步调用模型gofunc(){deferclose(modelCh)//调用DeepSeek-R1模型进行流式生成err:=f.deepSeekClient.GenerateStream(modelCtx,modelRequest,func(chunk*DeepSeekChunk)error{ifchunk.Error!=nil{modelCh<-&DeepSeekResponse{Error:chunk.Error,}returnchunk.Error}//处理模型流式响应modelCh<-&DeepSeekResponse{Content:chunk.Content,IsFinal:chunk.IsFinal,ToolCalls:chunk.ToolCalls,GeneratedText:chunk.GeneratedText,Usage:chunk.Usage,}returnnil})iferr!=nil&&!errors.Is(err,context.Canceled){f.log.Errorf("DeepSeek-R1模型调用失败:%v",err)modelCh<-&DeepSeekResponse{Error:err,}}}()//5.处理模型响应varfullContentstrings.BuilderisFirstChunk:=truefor{select{case<-ctx.Done()://处理超时f.log.Warnf("请求处理超时:%s",msgIdStr)output.Status=StatusTimeoutoutput.Message="处理超时,请稍后重试"//通过SSE发送超时事件sseWriter.WriteEvent(&SSEEvent{Event:"timeout",Data:map[string]interface{}{"msg_id":msgIdStr,"message":"处理超时,请稍后重试",},})//更新消息状态f.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusFailed)returncaseresp,ok:=<-modelCh:if!ok{//处理响应结束gotoEND}//处理模型返回的错误ifresp.Error!=nil{f.log.Errorf("模型返回错误:%v",resp.Error)output.Status=StatusFailedoutput.Message="AI生成回答失败"//通过SSE发送错误事件sseWriter.WriteEvent(&SSEEvent{Event:"error",Data:map[string]interface{}{"msg_id":msgIdStr,"message":"AI生成回答失败",},})f.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusFailed)return}//处理模型返回的数据包//追加内容、安全检查、发送给客户端等content:=resp.Content//安全检查每个片段iflen(content)>0{safeResult,_:=f.safetyCheck(ctx,content)if!safeResult.IsSafe{f.log.Warnf("模型回复内容存在安全风险:%s",content)content="对不起,我无法提供这方面的回答。"}}//追加到完整内容fullContent.WriteString(content)//如果是第一个数据包,更新消息状态为进行中ifisFirstChunk{isFirstChunk=falsef.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusInProgress)//返回初始响应给客户端output.Status=StatusSuccessoutput.AnswerBegin=content//通过SSE发送开始事件sseWriter.WriteEvent(&SSEEvent{Event:"answer_begin",Data:map[string]interface{}{"msg_id":msgIdStr,"content":content,},})}else{//非首个数据包,通过SSE发送内容片段sseWriter.WriteEvent(&SSEEvent{Event:"answer_chunk",Data:map[string]interface{}{"msg_id":msgIdStr,"content":content,},})}//如果响应中包含特殊标记,处理特殊逻辑ifresp.HasSpecialFunction{f.handleSpecialFunction(ctx,resp.SpecialFunction,msgIdStr,req.TalId,sseWriter)}case_,ok:=<-interruptCh:if!ok{continue}//处理用户中断f.log.Infof("用户中断请求:%s",msgIdStr)msg.Msg.IsInterrupt=1//通过SSE发送中断事件sseWriter.WriteEvent(&SSEEvent{Event:"interrupted",Data:map[string]interface{}{"msg_id":msgIdStr,},})f.handelInterrupt(ctx,msgIdStr,msg.SubjectId)gotoEND}}END://处理结束逻辑//生成提示词、更新消息等finalContent:=fullContent.String()//更新最终消息内容和状态err=f.repo.UpdateMessageContent(ctx,msgIdStr,finalContent)iferr!=nil{f.log.Errorf("更新消息内容失败:%v",err)}ifmsg.Msg.IsInterrupt==0{//正常结束f.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusCompleted)//发送完成事件sseWriter.WriteEvent(&SSEEvent{Event:"answer_complete",Data:map[string]interface{}{"msg_id":msgIdStr,"content":finalContent,},})//记录会话历史f.updateSessionHistory(ctx,req.TalId,req.Question,finalContent,msgIdStr)}else{//中断结束f.repo.UpdateMessageStatus(ctx,msgIdStr,MessageStatusInterrupted)}//设置输出信息output.FullAnswer=finalContentoutput.Status=StatusSuccessoutput.Suggestions=suggestionsoutput.Knowledge=knowledge}

      处理意图识别特殊逻辑:

      //处理特殊意图func(f*FreeAskUsecase)handleSpecialIntent(ctxcontext.Context,intentstring,detailsmap[string]interface{},req*FreeAskReq,output*FreeAskResp)bool{switchintent{case"greeting"://处理问候意图output.FullAnswer=f.generateGreeting(req.TalId,details)output.Status=StatusSuccessreturntruecase"homework_submission"://处理作业提交意图ifsubjectID,ok:=details["subject_id"].(string);ok{//重定向到作业提交服务redirectInfo:=f.homeworkService.GetSubmissionRedirect(ctx,req.TalId,subjectID)output.RedirectInfo=redirectInfooutput.Status=StatusRedirectreturntrue}case"schedule_query"://处理日程查询意图ifdate,ok:=details["date"].(string);ok{scheduleInfo:=f.scheduleService.GetSchedule(ctx,req.TalId,date)output.FullAnswer=f.formatScheduleResponse(scheduleInfo)output.Status=StatusSuccessoutput.StructuredData=scheduleInforeturntrue}case"calculator_request"://处理计算器请求ifexpression,ok:=details["expression"].(string);ok{result,err:=f.calculatorService.Calculate(ctx,expression)iferr==nil{output.FullAnswer=fmt.Sprintf("计算结果是:%s",result)output.Status=StatusSuccessoutput.StructuredData=map[string]interface{}{"type":"calculation","expression":expression,"result":result,}returntrue}}}//如果不是特殊意图或者处理失败,返回false继续常规处理returnfalse}
      构建系统提示词:
      //构建系统提示词func(f*FreeAskUsecase)buildSystemPrompt(subjectIdint32,intentstring,intentDetailsmap[string]interface{})string{varbuilderstrings.Builder//基础教学角色定义builder.WriteString("你是一位专业的教育助手,名为「xxx学习助手」。")//根据学科添加特定指引switchsubjectId{case1://数学builder.WriteString("你擅长数学教学,能够用清晰的方式解释数学概念和解题步骤。请注重培养学生的数学思维和解题能力。")case2://语文builder.WriteString("你擅长语文教学,能够帮助分析文章含义、解释字词、指导写作。请注重培养学生的语言表达和理解能力。")case3://英语builder.WriteString("你擅长英语教学,能够帮助学生掌握英语知识、理解语法规则、提升语言运用能力。请用中文回答,适当穿插英文解释。")case4://物理builder.WriteString("你擅长物理教学,能够解释物理概念、公式和物理现象。请注重培养学生的科学思维和实验精神。")default:builder.WriteString("你能够提供全面的学科指导,包括答疑解惑、知识讲解和学习方法指导。")}//添加教学风格和方法指引builder.WriteString("\n\n请遵循以下教学原则:")builder.WriteString("\n1.循序渐进:从简单到复杂,确保学生能够理解每一步")builder.WriteString("\n2.举一反三:通过类比和例子帮助理解")builder.WriteString("\n3.启发式教学:引导学生思考,而不是直接给出答案")builder.WriteString("\n4.耐心友好:使用鼓励性语言,营造积极学习氛围")//根据意图添加特定指引ifintent=="homework_help"{builder.WriteString("\n\n当前学生正在寻求作业帮助,请引导他们理解问题,提供解题思路,但不要直接给出完整答案。")}elseifintent=="concept_explanation"{builder.WriteString("\n\n当前学生正在寻求概念解释,请用通俗易懂的语言和生动的例子解释相关概念。")}//添加输出格式要求builder.WriteString("\n\n回答格式要求:")builder.WriteString("\n-开始时简要总结问题")builder.WriteString("\n-分步骤详细解释")builder.WriteString("\n-适当使用公式、图表等辅助说明")builder.WriteString("\n-结尾总结要点或给出拓展思考")returnbuilder.String()}

      四、总结


      本文主要从内容审核、用户意图识别、提示词构建、历史消息、模型调用、中断处理、存储层实现等模块详细的介绍了大模型应用中多轮对话系统的实现,真正看懂了,你能对多轮对话有一个整体的认识。


      回复

      使用道具 举报

      您需要登录后才可以回帖 登录 | 立即注册

      本版积分规则

      链载AI是专业的生成式人工智能教程平台。提供Stable Diffusion、Midjourney AI绘画教程,Suno AI音乐生成指南,以及Runway、Pika等AI视频制作与动画生成实战案例。从提示词编写到参数调整,手把手助您从入门到精通。
      • 官方手机版

      • 微信公众号

      • 商务合作

      • Powered by Discuz! X3.5 | Copyright © 2025-2025. | 链载Ai
      • 桂ICP备2024021734号 | 营业执照 | |广西笔趣文化传媒有限公司|| QQ