基于SSE技术加 deepseek 实现打字机回复效果
具体代码,已上传到厂库
git clone https://gitee.com/rush_peng/sse-deepseek-demo.git
SSE 技术
go 后端实现
package main
import (
"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 []Message
const (
AskTypeCompletion = "completion"
AskTypeChat = "chat"
)
// deepseekStream 通过Deepseek API进行流式生成响应
func deepseekStream(w http.ResponseWriter, prompt string, askType string) (err error) {
var apiURL string
var completeResponse strings.Builder // 存储模型完整回复,使用strings.Builder 高效拼接字符串
var getResContest func(resp DeepseekResponse) string
reqBody := 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 = prompt
getResContest = 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 = chartHistory
getResContest = 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 DeepseekResponse
if 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-stream
w.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))
}
浙公网安备 33010602011771号