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

推荐订阅源

云风的 BLOG
云风的 BLOG
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
TaoSecurity Blog
TaoSecurity Blog
Attack and Defense Labs
Attack and Defense Labs
SecWiki News
SecWiki News
M
MIT News - Artificial intelligence
N
News and Events Feed by Topic
Help Net Security
Help Net Security
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
The Last Watchdog
The Last Watchdog
Martin Fowler
Martin Fowler
Hacker News: Ask HN
Hacker News: Ask HN
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
B
Blog
Blog — PlanetScale
Blog — PlanetScale
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
I
InfoQ
腾讯CDC
L
LangChain Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Know Your Adversary
Know Your Adversary
Cloudbric
Cloudbric
Project Zero
Project Zero
T
Tor Project blog
小众软件
小众软件
博客园 - 司徒正美
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Help Net Security
Webroot Blog
Webroot Blog
量子位
NISL@THU
NISL@THU
Schneier on Security
Schneier on Security
Google Online Security Blog
Google Online Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
月光博客
月光博客
宝玉的分享
宝玉的分享
V
V2EX
T
Tailwind CSS Blog
Spread Privacy
Spread Privacy
G
Google Developers Blog
K
Kaspersky official 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 中进行重定向配置 - 任峻宏的小站 记我的第二次部署 - 任峻宏的小站 写在“毕业”之际 - 任峻宏的小站 JS 简单实现弹出层效果 - 任峻宏的小站 速递易工作总结及感想 - 任峻宏的小站 在 Aliyun ECS 上用 Nginx + Passenger 部署 Rails 应用时安装 Passenger 的问题 [译] Common Rails Idioms that Kill Database Performance Git 常用操作总结 - 任峻宏的小站 [译] Terminal 功夫——方便开发者的实用技巧 - 任峻宏的小站 实例方法与类方法 - 任峻宏的小站 各种报错解决集合 - 任峻宏的小站
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