这一期 Rei on Rails 展示如何用 Rails 开发 ChatGPT 聊天应用。
着重讲了 Turbo Stream Broadcast 的用法,这个功能似乎很少被提及,但我觉得是一个杀手级功能。
前些天在老产品上面也添加了一个给老用户体验的, https://www.vcooline.com。
主要加了返回 stream 的处理(一个字一个字蹦出来)
def perform_openai_request(prompt:)
completion_content = ""
start_chunk_message
OpenaiApiProxy::ChatClient.new(api_key: ‘’, organization_id: ‘’).create(
model: "gpt-3.5-turbo",
messages: generate_chat_messages(prompt),
max_tokens:1234,
user: user.to_gid.to_s,
stream: true
) do |reqt|
reqt.options.on_data = Proc.new do |chunk, overall_received_bytes, env|
chunk_contents = handle_openai_chunk_data(chunk, overall_received_bytes, env)
chunk_contents.compact_blank.map { |chunk_content| completion_content.concat(chunk_content) }
end
end
completion_content.presence
&.tap { |content| decrease_usage_quota(content:) }
&.tap { |content| save_bot_message(content:) }
rescue OpenaiApiProxy::Client::ServerError, OpenaiApiProxy::Client::InternalError => e
Rails.logger.error "#{self.class.name} perform #{e.class.name}: #{e.message}"
send_chunk_message(content: "系统繁忙,请稍候再试。")
end
def handle_openai_chunk_data(chunk, _overall_received_bytes, _env)
chunk.force_encoding("UTF-8").encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
.split("\n\n")
.map { |line| line.sub(/^data:\ */, "") }
.compact_blank
.map.each_with_object([]) do |chunk_line, chunk_contents|
break chunk_contents if chunk_line == "[DONE]"
chunk_info = JSON.parse chunk_line
# start_chunk_message if chunk_info.dig("choices", 0, "delta", "role").eql?("assistant")
chunk_info.dig("choices", 0, "delta", "content").presence
&.tap { |chunk_content| chunk_contents.push(chunk_content) }
&.tap { |chunk_content| send_chunk_message(content: chunk_content) }
end
end
def send_chunk_message(content:)
start_chunk_input
message.broadcast_append_to \
ActionView::RecordIdentifier.dom_id(message.conversation, :namespace_example),
target: reply_message_dom_id,
html: content
end
ChatClient 就是一个 Faraday.new,太简单了所以没贴。细节在这里: https://github.com/as181920/openai_api_proxy/blob/main/lib/openai_api_proxy/chat_client.rb