惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
Vercel News
Vercel News
U
Unit 42
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
MyScale Blog
MyScale Blog
Y
Y Combinator Blog
阮一峰的网络日志
阮一峰的网络日志
爱范儿
爱范儿
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog RSS Feed
N
Netflix TechBlog - Medium
GbyAI
GbyAI
F
Fortinet All Blogs
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
M
MIT News - Artificial intelligence
D
Docker
IT之家
IT之家
Stack Overflow Blog
Stack Overflow Blog

任峻宏的小站

Debugging Memory Leaks in a Next.js Application Troubleshooting: Resolving Devise Issue in an API-only Application How to Customize a Right-Click Menu in React Using ActionCable and React to Create a Simple Chat App 纪念左耳朵耗子 - 任峻宏的小站 Refresh Myself - 任峻宏的小站 战痘 - 任峻宏的小站 逆向的能量——生活需要批评者 - 任峻宏的小站 人活着必须要有目标 - 任峻宏的小站 《第一人称单数》读书笔记 - 任峻宏的小站 卸载英雄联盟 - 任峻宏的小站 我的 2021 年总结 - 任峻宏的小站 记我的爷爷 - 任峻宏的小站 Migrate from Webpacker to Vite 从入门到放弃 《人生算法》读书笔记 - 任峻宏的小站 《不拘一格》读书笔记 - 任峻宏的小站 我为什么很少写博客 - 任峻宏的小站 我的2020年总结 - 任峻宏的小站 如何避免冲动消费? - 任峻宏的小站 《不能不去爱的两件事》读书笔记 - 任峻宏的小站 记一次搬家 - 任峻宏的小站 My blog V3.0 has been published! 解决服务器上运行 bundle install 时 killed 的问题 解决生产环境不能发送邮件的问题 - 任峻宏的小站 解决内存泄漏记录 - 任峻宏的小站 勿忘初心 - 任峻宏的小站 关于 RSpec 的一点方法总结 - 任峻宏的小站 使用 AJAX 重载部分页面 - 任峻宏的小站 Nginx 添加 SSL 支持 - 任峻宏的小站 Nginx 中进行重定向配置 - 任峻宏的小站
Using SSE to Implement ChatGPT in Rails
任峻宏 · 2023-04-30 · via 任峻宏的小站

中文版本 (Chinese version): https://ruby-china.org/topics/43052

Introduction

When using ChatGPT, you may notice that the response is not returned all at once after completion, but rather in chunks, as if the response was being typed out:

About SSE

If we check OpenAI API document, we can find that there's a param called stream for the create chat completion API.

If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message.

So what is SSE?

Basically, SSE, short for “Server-Sent Event”, is a simple way to stream events from a server. It is used for sending real-time updates from a server to a client over a single HTTP connection. With SSE, the server can push data to the client as soon as it becomes available, without the need for the client to constantly poll the server for updates.

SSE can be implemented through the HTTP protocol:

  1. The client make a GET request to the server: https://www.host.com/stream
  2. The client sets Connection: keep-alive to establish a long-lived connection
  3. The server sets a Content-Type: text/event-stream response header
  4. The server starts sending events that look like this:
event: add
data: This is the first message, it
data: has two lines.

Difference between SSE and WebSocket

It looks like SSE is similar to WebSocket? they are both used for real-time communication between a server and a client, but there are some differences between them.

  1. SSE provides unidirectional communication only (server -> client). WebSockets on the other hand give us real-time bidirectional communication.
  2. SSE is an HTTP-based technology, while WebSocket is a TCP-based technology. This means that SSE is built on top of the HTTP protocol and uses long polling techniques to achieve real-time communication, while WebSocket directly sends and receives data over the TCP connection, which can achieve faster real-time communication.
  3. Another difference is in how they handle re-connections. SSE automatically attempts to reconnect to the server if the connection is lost, whereas WebSocket requires the client to initiate a new connection if the connection is lost.

In conclusion, SSE seems like a simpler alternative to websockets if you only need to have the server send events. WebSocket, on the other hand, is more powerful and can be used in more complex scenarios, such as real-time chat applications or multi-player games.

Workflow

Now let's talk about how to use OpenAI's API to receive Server-Sent Events (SSE) on your server, and forward those events to your client using SSE.

Here is the workflow for implementing SSE in Rails to use ChatGPT:

  1. The client creates an SSE EventSource to server endpoint with SSE configured.
  2. The server receives the request and sends a request to OpenAI API using the stream: true parameter.
  3. The server listens for server-side events from the OpenAI API connection created in step 2. For each event received, the server can forward that message to the client. This keeps our API secret because all the communication to OpenAI happens on our server.
  4. After the client receives the entire response, OpenAI sends a special message to let us know to close the connection. The [Done] message signals that we can close the SSE connection to OpenAI, and our client can close the connection to our server.

Use Rails as server API

After understanding SSE and the workflow, we start coding the entire process.

  • client setup
const fetchResponse = () => {
  const evtSource = new EventSource(`/v1/completions/live_stream?prompt=${prompt}`)
  evtSource.onmessage = (event) => {
    if (event) {
      const response = JSON.parse(event.data)
      setMessage(response)
    } else {
      evtSource.close()
    }
  }
  evtSource.onerror = () => {
    evtSource.close()
  }
}

We uses the EventSource API to establish a server-sent event connection. And the onmessage event will be triggered when a message is received from the server.

  • server setup
class CompletionsController < ApplicationController
  include ActionController::Live

  def live_stream
    response.headers["Content-Type"] = "text/event-stream"
    response.headers["Last-Modified"] = Time.now.httpdate
    sse = SSE.new(response.stream, retry: 300)
    ChatCompletion::LiveStreamService.new(sse, live_stream_params).call
  ensure
    sse.close
  end
end

We include the ActionController::Live module to enable live streaming.

As we mentioned above, the content-type response headers should be set to text/event-stream.

Please note that the stream response in Rails 7 does not work by default due to a rack issue, you can check for more details on this issue.

it took me hours to find out the issue is related to rack… Rails includes Rack::ETag by default, which will buffer the live response.

anyway, this line is necessary if your rack version is 2.2.x:

response.headers["Last-Modified"] = Time.now.httpdate

  • OpenAI API
module ChatCompletion
  class LiveStreamService
    def call
      client.create_chat_completion(request_body) do |chunk, overall_received_bytes, env|
        data = chunk[/data: (.*)\n\n$/, 1]
        send_message(data)
      end
    end

    def send_message(data)
      response = JSON.parse(data)
      if response.dig("choices", 0, "delta", "content")
        @result = @result + response.dig("choices", 0, "delta", "content")
      end
      sse.write(status: 200, content: @result)
    end

    private

    def client
      @client ||= OpenAI::Client.new(OPENAI_API_KEY)
    end
  end
end

The code above uses an OpenAI gem to send request to OpenAI API, it's a simple Ruby wrapper and support streaming response.

BTW, If you're using Hotwire in Rails, you can check this guide.

That's all! Thanks for reading!

demo website: aiichat.cn