Bỏ qua để vào nội dung chính
Chuyển app từ Anthropic SDK sang OpenAI Responses API

Chuyển app từ Anthropic SDK sang OpenAI Responses API

Bởi Zoe Taylor
02 thg 10, 20267 phút đọc

Bản đồ đổi tên field-by-field giữa Anthropic SDK và OpenAI Responses API, chỗ duy nhất phải viết lại, và quy trình đo chi phí thật trước khi cắt over.

Khách hàng yêu cầu bạn chạy thêm OpenAI bên cạnh Claude, và bạn đang nhìn vào codebase tự hỏi mất bao lâu. Đội bên cạnh thì bảo phải viết lại toàn bộ tầng gọi model. Thực tế hẹp hơn nhiều: phần lớn là đổi tên field, và chỉ có đúng một chỗ phải thiết kế lại — vòng lặp tool-calling.

Bài viết này dựng lại bản đồ chuyển đổi field-by-field từ một hướng dẫn migration trên Dev.to, cộng với quy trình đo chi phí thật từ một bài thứ hai, để bạn biết chính xác cần sửa gì và cần đo gì trước khi cắt over.

Một call, hai SDK

Điểm khởi đầu là một request duy nhất. Nguồn đặt hai đoạn code cạnh nhau — ví dụ minh hoạ của tác giả, không phải code production của bạn:

# Anthropic
resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=512,
    system="You are terse.",
    messages=[{"role": "user", "content": "Define idempotency."}],
)
text = next(b.text for b in resp.content if b.type == "text")

# OpenAI (Responses API)
resp = client.responses.create(
    model="gpt-5.5",
    max_output_tokens=512,
    instructions="You are terse.",
    input="Define idempotency.",
)
text = resp.output_text

Tác giả tóm tắt: "Both do the identical thing. Every change is a rename" — và phần đọc kết quả rút gọn từ vòng lặp qua các content block xuống còn một thuộc tính resp.output_text.

Bản đồ đổi tên đầy đủ

Hạng mụcAnthropicOpenAI Responses
ClientAnthropic()OpenAI()
Lời gọiclient.messages.create(...)client.responses.create(...)
System promptsystem=instructions=
Nội dung usermessages=[{role, content}]input= (string hoặc list)
Giới hạn outputmax_tokensmax_output_tokens
Đọc textlặp qua resp.contentresp.output_text
Structured outputmessages.parse(output_format=…)responses.parse(text_format=…)
Định nghĩa tool{name, input_schema}{"type": "function", name, parameters}
Tool requesttool_use block + tool_use_idfunction_call item + call_id
Trả kết quả tooltool_result trong user messagefunction_call_output item
Điều khiển vòng lặpstop_reason == "tool_use"còn function_call trong resp.output
Reasoningthinking + output_config.effortreasoning={"effort": …}
Exceptionanthropic.APIErroropenai.APIError

Nếu service của bạn chỉ sinh text, đây là toàn bộ công việc. Tác giả gọi đó là thay đổi năm phút.

Chỗ duy nhất phải thiết kế lại: vòng lặp tool

Khác biệt cấu trúc nằm ở cách hai bên báo "tôi muốn gọi tool". Theo nguồn: Claude rẽ nhánh trên stop_reason và khớp các tool_use block bằng tool_use_id; OpenAI không có stop_reason, nên bạn lặp chừng nào resp.output còn chứa function_call item và khớp bằng call_id.

Về mặt kỹ thuật, điều kiện dừng của bạn chuyển từ một phép so sánh enum sang một phép kiểm tra trên danh sách. Nếu code hiện tại của bạn viết while stop_reason == "tool_use", chỗ đó không dịch thẳng được — phải viết lại thành kiểm tra sự tồn tại của item trong output. Đây là nơi đáng viết test trước khi đổi, vì một vòng lặp sai điều kiện dừng sẽ hoặc treo, hoặc trả lời khi tool chưa chạy xong.

Bỏ bùa chú chain-of-thought, dùng nút effort

Nguồn nêu một thay đổi về prompt mà nhiều đội bỏ qua. Trên các model cũ, "let's think step by step" từng giúp ích đo được cho bài toán khó. Trên các reasoning model hiện đại, theo tác giả, câu đó thường thừa — model suy luận bên trong, và bạn điều chỉnh mức độ bằng một tham số chứ không phải bằng một câu thần chú:

resp = client.responses.create(
    model="gpt-5.5",
    reasoning={"effort": "high"},
    input=prompt,
)

Khuyến nghị của nguồn: đừng port nguyên các câu chain-of-thought cũ; nâng effort cho tác vụ khó và giữ prompt sạch.

Đo chi phí thật, không đọc bảng giá

Lý do phổ biến nhất để chạy hai provider là chi phí — và đây là chỗ dễ tự lừa mình nhất. Một bài hướng dẫn thứ hai, của một tác giả có công bố lợi ích liên quan (tự khai làm việc tại một dịch vụ API trả trước, và nói rõ bài viết là về phương pháp so sánh chứ không khẳng định dịch vụ nào rẻ nhất), đưa ra lập luận đáng dùng:

Giá trên mỗi triệu token là không đủ để so sánh hai API. Một dịch vụ có thể công bố rate riêng cho input, cached-input và output; dịch vụ khác công bố một rate gộp. Các con số mô tả những công thức tính tiền khác nhau cho tới khi bạn test chúng bằng cùng một workload.

Với biểu giá tách, bài viết ước lượng một request theo công thức: input_tokens × input_rate + cached_input_tokens × cached_rate + output_tokens × output_rate. Kết quả cuối cùng, theo tác giả, phụ thuộc vào tỉ lệ input/output của workload, tỉ lệ cache hit, lượng reasoning token, phí tối thiểu mỗi request và các lần retry hỏng.

Quy trình đo mà nguồn đề xuất, rút gọn cho một buổi chiều:

  • Dựng ba hình dạng workload: một prompt tương tác ngắn, một tác vụ coding long-context, và một tác vụ nặng output. Nếu cache quan trọng, chạy cả request lạnh lẫn request lặp lại cùng prefix — đừng giả định có cache hit chỉ vì hai prompt trông giống nhau.
  • Ghi lại cho từng test: request ID, model và endpoint chính xác; usage input/cached-input/output/reasoning khi có báo; time to first token, tổng latency, và liệu stream có chạm tới sự kiện kết thúc hay không; khoản tính tiền cuối cùng sau đối soát; mọi retry, timeout hay phản hồi dở dang.
  • Lấy model ID từ API, không copy tên model từ một bài viết cũ.
  • Đặt trần ngân sách nhỏ khi thử nghiệm, tách API key test khỏi production, và bật giới hạn chi tiêu hoặc rate ở nơi có hỗ trợ.

Một cảnh báo cụ thể đáng chú ý: stream có thể hỏng sau khi đã sinh ra text hữu ích, hoặc ngắt trước khi usage cuối cùng về. Hãy quyết định ứng dụng xử lý tình huống đó ra sao và kiểm tra bản ghi tính tiền trước khi retry — nếu không, retry sẽ làm méo cả so sánh chi phí lẫn so sánh độ tin cậy.

Thứ tự cắt over

Nguồn thứ nhất đưa ra một trình tự bốn bước, và nhấn mạnh rằng migration "chưa xong khi code compile — nó xong khi eval pass":

  1. Đổi client và bề mặt lời gọi theo bảng trên.
  2. Chuyển system sang instructions, viết lại vòng lặp tool.
  3. Đưa model id về một giá trị config duy nhất.
  4. Chạy lại bộ eval trên phiên bản OpenAI — lý tưởng là sau một flag, để chạy song song và so sánh trước khi cắt.

Lưu ý đi kèm: model khác nhau hành xử khác nhau, nên prompt đã tinh chỉnh trên Claude có thể cần chỉnh lại trên GPT. Bước 4 không phải thủ tục — nó là bước duy nhất phát hiện ra sự khác biệt đó.

Việc nên làm tuần này

Nếu bạn chưa có bộ eval, dựng nó trước khi dựng bản đồ migration — không có eval thì bạn không có cách nào biết provider thứ hai trả lời tệ hơn hay chỉ khác đi. Nếu đã có, hãy đo ba hình dạng workload ở trên trên cả hai provider trong cùng một buổi, công bố giả định và ngày test kèm kết quả, và giữ model id trong config phía sau một interface. Khi đó, như nguồn kết luận, "Claude hay OpenAI hay cả hai" trở thành một quyết định cấu hình, không phải một đợt viết lại.

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan