Di chuyển từ Chat Completions sang Responses API
Hướng dẫn từng bước để chuyển mã OpenAI Chat Completions sang Responses API: các mục đầu vào, phân tích đầu ra, trạng thái, công cụ, streaming và một kế hoạch quay lui.
Di chuyển từ Chat Completions sang Responses API chủ yếu là công việc đổi tên và định hình lại: POST /v1/chat/completions trở thành POST /v1/responses, mảng messages trở thành input, lời nhắc hệ thống trở thành instructions, và bạn đọc response.output_text thay vì completion.choices[0].message.content. Những phần khiến người ta vấp ngã là những phần thay đổi hành vi chứ không phải tên gọi — các mục đầu ra, tính có trạng thái, việc liên kết công cụ và các trường đầu ra có cấu trúc. Hướng dẫn này đi qua quá trình di chuyển theo đúng thứ tự mà bạn thực sự nên làm, kèm một kế hoạch quay lui để bạn có thể chuyển từng điểm cuối một, thay vì viết lại tất cả cùng lúc.
Bạn không bắt buộc phải di chuyển. Chat Completions vẫn được hỗ trợ. Hãy di chuyển khi bạn muốn tính liên tục trong suy luận, trạng thái hội thoại do máy chủ quản lý, các công cụ gốc, hoặc đầu ra được định kiểu gọn gàng hơn mà các vòng lặp tác tử được hưởng lợi. Nếu bạn vẫn đang cân nhắc, hãy đọc Responses API so với Chat Completions trước.
Bước 0: Thiết lập một chuẩn tham chiếu để đối chiếu
Trước khi chạm vào mã, hãy ghi lại trạng thái "đúng" trên điểm cuối hiện tại. Gửi một vài lời nhắc tiêu biểu qua Chat Completions và lưu văn bản phản hồi, mức sử dụng token cùng độ trễ. Định tuyến chúng qua gateway để các dấu vết được ghi vào nhật ký sử dụng. Sau đó, bạn sẽ chạy lại chính những lời nhắc đó với Responses và so sánh. Những lần di chuyển bỏ qua bước này sẽ biến thành phỏng đoán ngay khi đầu ra khác đi.
Bước 1: Đổi tên điểm cuối và di chuyển lời nhắc hệ thống
Cuộc di chuyển hoạt động nhỏ nhất chỉ gồm ba chỉnh sửa. Trỏ SDK đến phương thức Responses, di chuyển bất kỳ thông điệp system nào vào instructions, và gộp một lượt người dùng đơn lẻ vào input.
Trước:
completion = client.chat.completions.create(
model="gpt-5.6",
messages=[
{"role": "system", "content": "You are a release-notes summarizer."},
{"role": "user", "content": "Summarize the changelog below:\n" + changelog},
],
)
text = completion.choices[0].message.content
Sau:
response = client.responses.create(
model="gpt-5.6",
instructions="You are a release-notes summarizer.",
input="Summarize the changelog below:\n" + changelog,
)
text = response.output_text
input chấp nhận hoặc một chuỗi thuần (như trên) hoặc một mảng các mục được định kiểu khi bạn cần lịch sử nhiều lượt hoặc kết quả công cụ. Hãy bắt đầu bằng dạng chuỗi để có một bài kiểm thử xanh, rồi chỉ định hình lại ở nơi bạn cần cấu trúc.
Bước 2: Phân tích các mục đầu ra, không phải choices[0]
Đây là bước thay đổi hành vi. Chat Completions cho bạn choices, và chỉ số 0 gần như luôn là câu trả lời. Responses trả về một mảng output gồm các mục có kiểu — message, reasoning và function_call — còn câu trả lời không được bảo đảm nằm ở vị trí đầu tiên. Một mô hình suy luận có thể phát ra một mục reasoning trước message, và một lượt dùng công cụ có thể phát ra function_call mà không có văn bản nào. (function_call_output, dùng để gửi kết quả công cụ trở lại, là một mục đầu vào do bạn tạo chứ không phải mục được mô hình trả về; đừng nhầm hai phía này.)
Bộ truy cập tiện lợi response.output_text xử lý trường hợp phổ biến "chỉ cần cho tôi văn bản". Khi bạn cần kiểm soát nhiều hơn, hãy lặp theo type:
answer, tool_calls = None, []
for item in response.output:
if item.type == "message":
answer = item.content[0].text
elif item.type == "function_call":
tool_calls.append(item)
Đồng thời cập nhật việc hạch toán tiêu thụ của bạn: prompt_tokens/completion_tokens trở thành input_tokens/output_tokens trên đối tượng usage. Bất kỳ mã tính phí hay bảng điều khiển nào đọc số lượng token đều phải xử lý cả hai tên trường trong quá trình triển khai theo giai đoạn.
Bước 3: Quyết định cách bạn quản lý trạng thái hội thoại
Chat Completions không có trạng thái — bạn gửi lại toàn bộ lịch sử ở mỗi lần gọi. Responses được lưu trữ theo mặc định và trả về một id mà bạn có thể nối chuỗi từ đó. Hãy chọn một trong ba chiến lược và áp dụng nhất quán:
| Chiến lược | Cách làm | Dùng khi |
|---|---|---|
previous_response_id | Gửi id của phản hồi trước ở lần gọi tiếp theo | Bạn muốn máy chủ giữ lịch sử |
| Mục thủ công | Chuyển các mục output trước đó trở lại vào input | Bạn muốn kiểm soát hoàn toàn những gì được gửi lại |
| Không trạng thái | store: false và tự gửi lại lịch sử | Không lưu giữ, hoặc bạn đã tự quản lý một kho lưu trữ |
Hai điều cần lên kế hoạch trước. Thứ nhất, instructions không được mang theo qua previous_response_id, nên hãy gửi lại chúng ở mỗi lượt. Thứ hai, việc nối chuỗi không làm giảm hóa đơn của bạn: tất cả token đầu vào trước đó trong chuỗi đều được tính phí như token đầu vào. Nối chuỗi tiết kiệm băng thông và mã soạn sẵn, chứ không phải tiền. Để thực sự giảm chi phí ngữ cảnh, hãy kết hợp việc di chuyển với bộ nhớ đệm lời nhắc.
Bước 4: Chuyển các lệnh gọi công cụ và đầu ra có cấu trúc
Gọi hàm vẫn sống sót qua cuộc di chuyển, nhưng cách đấu nối thay đổi. Trong Chat Completions bạn nối thêm một thông điệp role: "tool" với một tool_call_id. Trong Responses bạn gửi một mục function_call_output được liên kết bằng call_id:
client.responses.create(
model="gpt-5.6",
previous_response_id=response.id,
input=[{
"type": "function_call_output",
"call_id": call.call_id,
"output": tool_result,
}],
)
Gửi kết quả mà không có call_id khớp là một trong những lỗi di chuyển phổ biến nhất, vì vậy hãy kiểm tra điều này trong bộ thử nghiệm. Đầu ra có cấu trúc cũng đổi chỗ: response_format trở thành text.format, và bạn phải đặt strict: true rõ ràng trên schema — đồng thời xác minh JSON Schema vẫn hợp lệ trong tập con nghiêm ngặt. (Đừng mang cơ chế bỏ strict để hạ xuống chế độ không nghiêm ngặt từ định nghĩa công cụ sang đây; với đầu ra có cấu trúc, hãy khai báo tường minh.) Chi tiết nằm trong đầu ra có cấu trúc với JSON Schema, còn các định nghĩa dùng được giữa nhiều nhà cung cấp nằm trong gọi công cụ khả chuyển giữa các nhà cung cấp.
Bước 5: Cập nhật các bên tiêu thụ streaming
Nếu bạn stream, phương tiện truyền tải vẫn là Server-Sent Events nhưng các sự kiện thay đổi. Chat Completions stream các đối tượng chat.completion.chunk với choices[].delta và kết thúc bằng data: [DONE]. Responses stream các sự kiện ngữ nghĩa như response.created, response.output_text.delta và response.completed. Các sự kiện ngữ nghĩa dễ định tuyến hơn vì mỗi sự kiện tự đặt tên cho chính nó, nhưng bộ phân tích [DONE]-và-delta hiện có của bạn sẽ không hiểu chúng nếu không thay đổi. Các mẫu phân tích cho cả hai đều có trong streaming phản hồi LLM với SSE.
Bước 6: Triển khai sau một cờ, giữ một đường quay lui
Đừng chuyển tất cả lệnh gọi cùng một lúc. Hãy đặt lựa chọn điểm cuối sau một cờ tính năng hoặc định tuyến theo từng mô hình tại gateway, đăng ký các upstream phía sau nó, rồi gửi một phần nhỏ lưu lượng đến Responses. So sánh chuẩn tham chiếu ở Bước 0 với các dấu vết mới: câu trả lời tương đương trong phạm vi chấp nhận được, số token hợp lý và độ trễ phù hợp. Nếu có hồi quy, bạn chỉ cần lật cờ về Chat Completions thay vì triển khai lại, vì khác biệt phía client đã được cô lập. Khi phần lưu lượng thử nghiệm hoạt động ổn định, hãy mở rộng dần.
Những gì cuộc di chuyển không thay đổi
Biết được chỗ nào bạn có thể thả lỏng là điều hữu ích. Một số thứ chuyển qua nguyên vẹn, nên bạn không nên viết lại chúng:
- Xác thực. Cùng khóa API và tiêu đề
Authorization: Bearervẫn hoạt động; chỉ đường dẫn thay đổi. - Định danh mô hình. Một mô hình như
gpt-5.6có cùng tên trên cả hai điểm cuối — bạn đang thay đổi lớp vỏ yêu cầu, không phải mô hình. - Nội dung lời nhắc. Các chỉ dẫn thực tế và văn bản người dùng của bạn là giống hệt nhau; chúng chỉ di chuyển từ các mục
messagessanginstructionsvàinput. - Nhiệt độ và các núm lấy mẫu. Các tham số sinh phổ biến giữ nguyên tên và ý nghĩa của chúng.
- Phương tiện truyền tải cho streaming. Cả hai điểm cuối đều stream qua Server-Sent Events, nên bộ đọc SSE của bạn vẫn giữ nguyên; chỉ các hình dạng sự kiện mà nó phân tích là khác.
Biết điều này giúp cuộc di chuyển giữ đúng phạm vi. Bề mặt hành vi thực sự thay đổi thì nhỏ: cấu trúc đầu ra, quản lý trạng thái, trường kết quả công cụ, và cấu hình đầu ra có cấu trúc. Mọi thứ khác là một sự dịch chuyển máy móc. Khi ước lượng công việc, hãy dành ngân sách cho bốn khu vực đó và coi phần còn lại là sao chép-dán, đó là lý do vì sao triển khai theo từng điểm cuối là thực tế chứ không phải một cuộc viết lại kiểu nổ lớn.
Đọc hiểu các lỗi di chuyển phổ biến
Hầu hết các lỗi di chuyển tạo ra một số ít triệu chứng có thể nhận ra. Văn bản trống hoặc thiếu thường có nghĩa là bạn đã đọc output[0] và rơi vào một mục reasoning hoặc function_call thay vì một message — hãy lặp theo type hoặc dùng output_text. Một lỗi 400 liên quan đến lược đồ trên đầu ra có cấu trúc thường có nghĩa là bạn để nguyên response_format thay vì chuyển sang text.format. Một vòng lặp công cụ bị treo hoặc lỗi thường có nghĩa là một function_call_output đã được gửi đi mà không có call_id khớp. Và một cuộc hội thoại "quên" vai trò hoặc giọng điệu của nó qua các lượt thường có nghĩa là bạn đã dựa vào việc instructions tồn tại qua previous_response_id — nó không tồn tại, nên hãy gửi lại chúng. Giữ danh sách ngắn này bên cạnh bản diff của bạn sẽ biến những lỗi khó hiểu thành những lần tra cứu nhanh.
Danh sách kiểm tra di chuyển
- Điểm cuối đã đổi từ
/v1/chat/completionssang/v1/responses. - Thông điệp
systemđã chuyển sanginstructionsvà được gửi lại ở mỗi lượt nối chuỗi. - Đầu ra được đọc qua
output_texthoặc bằng cách lặp các mụcoutputtheotype. - Việc hạch toán token xử lý
input_tokens/output_tokens. - Đã chọn chiến lược trạng thái:
previous_response_id, mục thủ công, hoặcstore: false. - Kết quả công cụ được gửi dưới dạng
function_call_outputvới mộtcall_idkhớp. -
response_formatđược thay bằngtext.format; lược đồ được kiểm tra lại. - Các bên tiêu thụ streaming được cập nhật cho các sự kiện ngữ nghĩa.
- Việc triển khai được kiểm soát bằng cờ với một đường quay lui bằng một cú lật.
Câu hỏi thường gặp
Tôi có phải di chuyển trước một hạn chót nào không?
Chat Completions không được lên lịch ngừng hoạt động, nên không có hạn chót bắt buộc cho nó. Điểm cuối mà OpenAI đã đánh dấu ngừng dùng là Assistants API (bị ngừng dùng vào 2025-08-26, với ngày ngừng hoạt động được công bố là 2026-08-26), vốn là một bề mặt khác. Hãy di chuyển sang Responses vì các khả năng của nó, chứ không phải vì gấp gáp.
Trợ lý của tôi đôi khi trả về văn bản trống sau khi di chuyển. Tại sao?
Bạn gần như chắc chắn đang đọc output[0] và trúng một mục reasoning hoặc function_call thay vì một message. Hãy lặp output theo type, hoặc dùng response.output_text, vốn tự bỏ qua các mục không phải văn bản giúp bạn.
Tôi có thể tiếp tục gửi lại toàn bộ lịch sử thay vì dùng previous_response_id không?
Có. Đặt store: false và truyền lịch sử của bạn dưới dạng các mục input đúng như bạn đã làm với messages. Cách này giữ cho điểm cuối không trạng thái và là lựa chọn đúng đắn cho các yêu cầu không lưu giữ.
Xác minh trước trên một điểm cuối
Hãy lấy một điểm cuối không quan trọng, chỉ áp dụng Bước 1–2 (đổi tên và phân tích đầu ra), rồi chạy các lời nhắc chuẩn tham chiếu ở Bước 0 qua đó. Nếu văn bản khớp và số token trong nhật ký sử dụng của gateway trông hợp lý, bạn đã chứng minh được cơ chế di chuyển trên chính lưu lượng của mình trước khi chạm vào trạng thái, công cụ hay streaming.