0
0
Lập trình
Admin Team
Admin Teamtechmely

Cách Sửa Lỗi 500 Ngẫu Nhiên từ OpenAI trong Rails

Đăng vào 7 tháng trước

• 6 phút đọc

Giới thiệu

Khi bạn xây dựng ứng dụng phụ thuộc vào các API bên thứ ba, một điều chắc chắn là các API đó sẽ gặp lỗi vào một thời điểm nào đó.

Các vấn đề về mạng, lỗi máy chủ tạm thời hoặc hạn chế tốc độ đều có thể dẫn đến các yêu cầu thất bại. Một ứng dụng mạnh mẽ cần dự đoán những thất bại này và xử lý chúng một cách khéo léo.

Trong hướng dẫn này, chúng ta sẽ đi qua một tình huống thực tế mà tôi vừa gặp phải trong một dự án Rails của mình.

Ứng dụng của tôi sử dụng gem ruby-openai để tương tác với OpenAI API, và tôi nhận thấy rằng công việc nền tảng chịu trách nhiệm tạo phản hồi LLM thường xuyên thất bại với lỗi Faraday::ServerError.

Chúng ta sẽ xem cách tôi chẩn đoán vấn đề và sử dụng các tính năng tích hợp của Rails để làm cho các công việc nền tảng của mình trở nên đáng tin cậy hơn.

Vấn đề: Một Công việc Nền tảng Thất bại

Vấn đề bắt đầu với các công việc vào hàng "thất bại" của tôi. Lỗi luôn giống nhau: Faraday::ServerError: máy chủ đã phản hồi với trạng thái 500.

Dưới đây là một đoạn mã từ stack trace:

Copy
/usr/local/bundle/ruby/3.3.0/gems/faraday-2.13.1/lib/faraday/response/raise_error.rb:38:in `on_complete'
...
/rails/app/services/llm/assistant_response_service.rb:22:in `generate_response'
/rails/app/jobs/llm/assistant_response_job.rb:13:in `perform'
...

Dưới đây là phương thức generate_response chịu trách nhiệm thực hiện cuộc gọi API đến OpenAI:

Copy
# `app/services/assistant_response_service.rb`

class Llm::AssistantResponseService < Llm::BaseOpenAiService

    # ...

    def generate_response
        parameters = {
            model: DEFAULT_MODEL,
            input: @input_messages,
            tools: @tool_registry&.registered_tools || [],
            previous_response_id: chat.previous_response_id,
            text: {
                verbosity: "low"
            }
        }

        response = client.responses.create(parameters: parameters)
        handle_response(response)
    end

    # ...

end

Và đây là công việc nền tảng gọi nó:

Copy
# `app/jobs/llm/assistant_response_job.rb`
class Llm::AssistantResponseJob < ApplicationJob
    queue_as :default

    def perform(message_id)
        message = Message.includes(chat: :chatbot).find(message_id)
        chat = message.chat
        chatbot = chat.chatbot

        Llm::AssistantResponseService.new(
            input_message: message.content,
            chat: chat,
            chatbot: chatbot,
        ).generate_response
    end
end

Vấn đề không nằm ở mã của tôi, mà là một vấn đề ở phía OpenAI. Tuy nhiên, ứng dụng của tôi không xử lý tốt nó. Công việc sẽ thử một lần, thất bại và từ bỏ.

Vấn đề là không có xử lý cho Faraday::ServerError. Công việc đơn giản thất bại và được chuyển vào hàng đợi chết, yêu cầu can thiệp thủ công để thử lại.

Giải pháp: Tự động Thử lại với Active Job

Cách tốt nhất để xử lý các lỗi tạm thời như trạng thái 500 là đơn giản thử lại sau một khoảng thời gian ngắn. May mắn thay, Rails làm điều này trở nên đơn giản với tính năng retry_on.

Bước 1: Thêm Thử lại vào Công việc

Thay đổi đầu tiên và quan trọng nhất là thông báo cho công việc của chúng ta thử lại khi gặp Faraday::ServerError.

Tôi đã sửa đổi app/jobs/llm/assistant_response_job.rb như sau:

Copy
# app/jobs/llm/assistant_response_job.rb

class Llm::AssistantResponseJob < ApplicationJob
    queue_as :default

    # **********************
    # THÊM DÒNG NÀY ⬇️
    # **********************
    retry_on Faraday::ServerError, wait: :polynomially_longer, attempts: 3

    def perform(message_id)
        message = Message.includes(chat: :chatbot).find(message_id)
        chat = message.chat
        chatbot = chat.chatbot

        Llm::AssistantResponseService.new(
            input_message: message.content,
            chat: chat,
            chatbot: chatbot,
        ).generate_response
    end
end

Với dòng lệnh này, công việc sẽ:

  1. Bắt bất kỳ Faraday::ServerError nào xảy ra trong quá trình thực hiện.

  2. Tự động thêm vào hàng đợi để thực hiện lại sau.

  3. Chờ một khoảng thời gian tăng dần theo dạng đa thức giữa các lần thử lại (:polynomially_longer).

    :polynomially_longer là một chiến lược backoff tích hợp sẵn cho các lần thử lại trong Rails. Nó làm cho thời gian chờ giữa các lần thử lại ngày càng tăng dựa trên số lần thử đã thực hiện: wait_time = (executions ** 4) + (random_jitter) + 2. Ví dụ:

Copy
- **Thử lại đầu tiên:** khoảng **3 giây**
- **Thử lại thứ hai:** khoảng **18 giây**
- **Thử lại thứ ba:** khoảng **83 giây**
- **Thử lại thứ tư:** lâu hơn nhiều, và cứ thế.

Mục tiêu là cho hệ thống nhiều thời gian hơn để phục hồi trước khi thử lại, thay vì liên tục gửi yêu cầu đến API đang thất bại với một khoảng thời gian cố định.
  1. Thử lại tối đa 3 lần trước khi cuối cùng từ bỏ và chuyển đến hàng đợi công việc thất bại.

Điều này ngay lập tức làm cho công việc của chúng ta trở nên mạnh mẽ hơn.

Bước 2: Cải thiện Ghi nhật ký Lỗi

Mặc dù việc thử lại rất tốt, nhưng chúng ta vẫn muốn biết khi nào những lỗi này xảy ra. Để khắc phục điều này, chúng ta cần bắt lỗi trong dịch vụ, ghi lại lỗi và sau đó tái phát nó để bộ xử lý retry_on của công việc có thể bắt được.

Dưới đây là phương thức generate_response đã được cập nhật trong app/services/llm/assistant_response_service.rb:

Copy
# app/services/llm/assistant_response_service.rb

class Llm::AssistantResponseService
# ...
    def generate_response
    # ...
    response = client.responses.create(parameters: parameters)
    handle_response(response)
    rescue Faraday::ServerError => e # <-- Thêm khối rescue này
        log_error(e, parameters) # <-- Ghi lại lỗi
        raise e # <-- Tái phát ngoại lệ
    end

    private

        def log_error(error, parameters = {})
            # Ghi lại lỗi vào dịch vụ theo dõi và ghi lại lỗi, ví dụ: Sentry
        end
end

Điểm mấu chốt ở đây là raise e. Nếu chúng ta chỉ bắt ngoại lệ mà không tái phát nó, công việc sẽ không bao giờ biết rằng một lỗi đã xảy ra và nó sẽ không thử lại. Bằng cách bắt, ghi lại và tái phát, chúng ta nhận được cả hai lợi ích: khả năng nhìn thấy các lỗi và tự động thử lại.

Kết luận

Bằng cách kết hợp retry_on của Active Job với xử lý lỗi cụ thể và ghi nhật ký, chúng ta đã xây dựng một công việc nền tảng đáng tin cậy.

Việc thực hiện điều này cực kỳ hiệu quả trong việc xử lý các yêu cầu mạng không đáng tin cậy đến các dịch vụ bên thứ ba, đảm bảo rằng người dùng của bạn sẽ có trải nghiệm mượt mà hơn và bạn sẽ tốn ít thời gian hơn trong việc thử lại các công việc thất bại.

Lần tới khi bạn làm việc với một API bên ngoài, hãy nhớ tự hỏi: "Điều gì xảy ra nếu điều này thất bại?" và xây dựng một chiến lược xử lý lỗi đáng tin cậy ngay từ đầu.

Nếu bạn thích hướng dẫn này, đây là nơi để tìm thêm công việc của tôi:

Untaught Blog
Đọc bài viết tại đây
Theo dõi tôi trên X

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào