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:
/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:
# `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ó:
# `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:
# 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ẽ:
-
Bắt bất kỳ
Faraday::ServerErrornào xảy ra trong quá trình thực hiện. -
Tự động thêm vào hàng đợi để thực hiện lại sau.
-
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_longerlà 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ụ:
- **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.
- 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:
# 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