基于SSE技术加 deepseek 实现打字机回复效果

news/2025/11/27 4:02:49/文章来源:https://www.cnblogs.com/rush-peng/p/19275118

具体代码,已上传到厂库

git clone https://gitee.com/rush_peng/sse-deepseek-demo.git

SSE 技术

go 后端实现

package mainimport ("bufio""bytes""encoding/json""fmt""log""net/http""strings""time"
)// Message 表示聊天消息结构
type Message struct {Role    string `json:"role"`Content string `json:"content"`
}// GenerateRequest 生成请求结构
type GenerateRequest struct {Model    string         `json:"model"`Prompt   string         `json:"prompt"`Stream   bool           `json:"stream"`Options  map[string]any `json:"options"`Messages []Message      `json:"messages"`
}// ResponseMessage 响应消息结构
type ResponseMessage struct {Role     string `json:"role"`Content  string `json:"content"` // chat模式下返回的内容Thinking string `json:"thinking"`
}// DeepseekResponse Deepseek API响应结构
type DeepseekResponse struct {Model      string          `json:"model"`Done       bool            `json:"done"`CreateAt   time.Time       `json:"create_at"`ResMessage ResponseMessage `json:"message"`Response   string          `json:"response"` // completion模式下返回的内容
}// cat 模式下, 需要存储历史聊天消息
var chartHistory []Messageconst (AskTypeCompletion = "completion"AskTypeChat       = "chat"
)// deepseekStream 通过Deepseek API进行流式生成响应
func deepseekStream(w http.ResponseWriter, prompt string, askType string) (err error) {var apiURL stringvar completeResponse strings.Builder // 存储模型完整回复,使用strings.Builder 高效拼接字符串var getResContest func(resp DeepseekResponse) stringreqBody := GenerateRequest{Model:  "deepseek-r1:8b",Stream: true,Options: map[string]any{"max_tokens":  1024,"temperature": 0.3,"top_p":       0.9,"raw":         true, // 这个如果false,会屏蔽 Thinking 字段},}switch askType {case AskTypeCompletion:apiURL = "http://localhost:11434/api/generate"reqBody.Prompt = promptgetResContest = func(resp DeepseekResponse) string {return resp.Response}case AskTypeChat:apiURL = "http://localhost:11434/api/chat"chartHistory = append(chartHistory, Message{Role: "user", Content: prompt})// 加入模型回复到历史记录defer func() {if err == nil {fmt.Println("模型的完整回复:", completeResponse.String())chartHistory = append(chartHistory, Message{Role: "assistant", Content: completeResponse.String()})}}()reqBody.Messages = chartHistorygetResContest = func(resp DeepseekResponse) string {return resp.ResMessage.Content}}fmt.Printf("接受到了发送到的请求....%+v\n", reqBody)data, _ := json.Marshal(reqBody)resp, err := http.Post(apiURL, "application/json", bytes.NewReader(data))if err != nil {return err}defer resp.Body.Close()reader := bufio.NewReader(resp.Body)flusher, _ := w.(http.Flusher)for {line, err := reader.ReadBytes('\n')if err != nil {break}lineStr := strings.TrimSpace(string(line))if lineStr == "" {continue}var part DeepseekResponseif err := json.Unmarshal(line, &part); err != nil {continue}fmt.Printf("原始返回:%+v\n", part)// 检查是否完成,输出完了,跳出循环if part.Done {break}if content := getResContest(part); content != "" {// 一般浏览器都兼容,直接发送字符串即可,不需要逐字符发送// for _, ch := range content {// 	fmt.Fprintf(w, "data: %c\n\n", ch) //将字节写入 io.write 中// 	flusher.Flush()// 	fmt.Println("输出后端返回结果:", content)// 	time.Sleep(1 * time.Millisecond) // 可调节打字机速度// }fmt.Fprintf(w, "data: %s\n\n", content) //将字节写入 io.write 中flusher.Flush()completeResponse.WriteString(content) // 累加模型回复}}fmt.Fprintf(w, "data: \n\n")fmt.Fprintf(w, "data: __END__\n\n") //自定义的结束符,前端代码根据这个结束符判断是否结束flusher.Flush()return nil
}// chatHandler 处理聊天请求的HTTP处理器
func chatHandler(w http.ResponseWriter, r *http.Request) {flusher, ok := w.(http.Flusher)if !ok {http.Error(w, "Streaming not supported", http.StatusInternalServerError)return}// sse 响应头设置,基本都是固定的w.Header().Set("Access-Control-Allow-Origin", "*")  // 测试过程中,允许所有域名跨域访问w.Header().Set("Content-Type", "text/event-stream") // 要实现打字机效果,事件流格式比如为 text/event-streamw.Header().Set("Cache-Control", "no-cache")         // 禁用缓存,确保实时更新w.Header().Set("Connection", "keep-alive")          // sse 保持连接userMessage := r.URL.Query().Get("msg")if userMessage == "" {userMessage = "Hello DeepSeek"}err := deepseekStream(w, userMessage, AskTypeChat)if err != nil {fmt.Fprintf(w, "data: %s\n\n", err.Error())flusher.Flush()}
}func main() {http.HandleFunc("/chat", chatHandler)fmt.Println("Server started at :8080")log.Fatal(http.ListenAndServe(":8080", nil))
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/977803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[zsh 专属配置 PS1 | 不依靠 oh my zsh]

# -------------------------- Zsh 专属 PS1 配置(含空行+conda/venv/git) -------------------------- # 颜色定义(Zsh用%F{颜色},而非Bash的\033) COLOR_CONDA="%F{cyan}" # 青色(conda环境) CO…

百航鹿大联训 roarctf_2019_easyheap

我现在心如死灰,面如平湖,胸有惊雷。 这里面水太深,你把握不住。😅 checksec是NO PIE的。来看代码。同样改了改函数名。 __int64 __fastcall main(int a1, char **a2, char **a3) {int v3; // eaxint v4; // ebxi…

图片云存储-七牛云

图片云存储-七牛云一、介绍 在实际开发中,我们会有很多处理不同功能的服务器。例如:应用服务器:负责部署我们的应用数据库服务器:运行我们的数据库文件服务器:负责存储用户上传文件的服务器  分服务器处理的目的…

彻底搞懂 Raft 算法:为“被理解”而生的分布式共识

彻底搞懂 Raft 算法:为“被理解”而生的分布式共识彻底搞懂 Raft 算法:为“被理解”而生的分布式共识 在分布式系统的世界里,共识(Consensus)是一个核心难题:如何让一堆机器对某个值(比如数据记录)达成一致? …

龙哥量化:初版简单的通达信公式回测案例_macd趋势策略收益曲线,展示在通达信的主副图(展示部分代码)

3、函数buy,sell不能指定手数,只能在界面设置。 4、不识别第二个参数价格,所以, 你用上图中的10个价格之外的价格,在回测系统不起作用,比如实时突破压力线,代码中用压力线的价格成交,BUY(突破, 压力线),实际…

代码随想录算法训练营第三章 哈希表part02

代码随想录算法训练营第三章 哈希表part02第454题.四数相加II leetcode链接:https://leetcode.cn/problems/4sum-ii/description/ 题目描述:给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, …

AI:情绪冲动和阴性症状有什么关系

情感症状 和情绪冲动和阴性症状有什么关系 这个问题非常深刻,触及了精神病理学的核心。情感症状、情绪冲动与阴性症状的关系,与它们和阳性症状的关系完全不同,甚至可以说是相反的。 简单来说:阳性症状是正常功能的…

龙哥量化:简单的通达信公式回测案例_macd趋势策略收益曲线,展示在通达信的主副图(展示部分代码)

3、函数buy,sell不能指定手数,只能在界面设置。 4、不识别第二个参数价格,所以, 你用上图中的10个价格之外的价格,在回测系统不起作用,比如实时突破压力线,代码中用压力线的价格成交,BUY(突破, 压力线),实际…

光缆地图网站

光缆地图网站几个全球公认最权威、最好用的光缆地图网站,各有特色: 1. 行业标准级:TeleGeography Submarine Cable Map 这是全球电信行业最权威的数据来源,也是 UI 做得最好看的。网址:https://www.submarinecabl…

AE表达式

--本篇导航--如何使用表达式(打开删除,查看图形,错误)表达式的数组、赋值输入表达式、优先级一些常用的表达式(value,time,index,wiggle,random,loopOut,Math,if…else)表达式控制一些现成的表达式(可以…

2025 Xhorse XDTPM1EN Universal Programmable TPMS Sensor: Supports 315/433MHz Key Tool Max Pro/MIDI

The 2025 Xhorse XDTPM1EN TPMS Sensor: Solving Your Tire Pressure Monitoring Challenges Problem Identification: The Frustrations of Traditional TPMS Sensors For European and American automotive professi…

2025年金蝶ERP服务商实施能力强、服务好——上海宝蝶深耕金蝶ERP管理系统、金蝶财务软件

随着企业数字化转型的加速,选择一款优秀的ERP系统是第一步,而找到一个具备超强实施能力的服务商,才是项目成功的关键。金蝶ERP作为市场主流选择,其代理商数量众多,但实施交付的专业度、成功率和行业适配性却天差地…

【论文阅读】DeltaLag: Learning Dynamic Lead-Lag Patterns in Financial Markets

【论文阅读】DeltaLag: Learning Dynamic Lead-Lag Patterns in Financial Markets在小红书上刷到的,之前没见过 lead-lag effect。这里算是头一次学习。 lead-lag effect 讲了一个简单的故事,例如 yubai 说一般 btc…

Xhorse XDTPM1EN Universal Programmable TPMS Sensor 4pcs/lot – 315/433MHz for Key Tool Max Pro/MIDI

The TPMS Challenge: Safety, Reliability, and Compliance at Stake In the world of automotive maintenance, tire pressure monitoring systems (TPMS) are non-negotiable for safety, legal compliance, and veh…

AI元人文:从价值对齐到价值共生的范式革命,及其在社会治理中的实践验证

AI元人文:从价值对齐到价值共生的范式革命,及其在社会治理中的实践验证 摘要: 本文旨在阐述“AI元人文”这一理论体系如何完成从哲学构想(“描述的哲学”)到实践方法论(“生成的语法”)的关键跃迁。我们通过一个…

深入解剖 Redis 分布式锁:从 SETNX 到 Redlock 的演进之路

深入解剖 Redis 分布式锁:从 SETNX 到 Redlock 的演进之路深入解剖 Redis 分布式锁:从 SETNX 到 Redlock 的演进之路摘要:在微服务与分布式架构中,“如何防止资源被并发抢占”是一个永恒的话题。从秒杀扣库存到定时…

闲话 25.11.26

那些你不要的:一道码力为主,没啥数学的 poly 题题解闲话 ZJUPH 恶心死我了。puzzle hunt 不好玩 😭 [数据删除] 敬请期待赛后 write-up。能完赛吗? 怎么快一个月没写鲜花了 /jk 正好投了一个题,写一下鲜花,顺便…

oop-实验4 - fg

task1 GradeCalc.hpp1 #pragma once2 3 #include<vector>4 #include<array>5 #include<string>6 7 class GradeCalc{8 public:9 GradeCalc(const std::string &cname); 10 void input(i…

揭开 Kafka 水位线的秘密:深度解析 LEO 与 HW 的同步机制

揭开 Kafka 水位线的秘密:深度解析 LEO 与 HW 的同步机制揭开 Kafka 水位线的秘密:深度解析 LEO 与 HW 的同步机制摘要:在分布式存储中,数据复制是保证高可用的核心。但你是否想过:Follower 是怎么把数据从 Leade…

INFINI Labs 产品更新 - Coco AI v0.9 与 Easysearch v2.0 全新功能上线,全面支持 GitLab 合并请求(MR)自动 AI Review

此次更新主要包括:Coco AI v0.9 全面支持 GitLab 合并请求(MR)自动 AI Review,并重构为插件流水线架构,新增 Neo4j、MongoDB 等 10+ 数据源连接器,开启“AI+开发”协同新范式;Easysearch v2.0 正式发布,内置轻…