cv.cm

AI · AI cho lập trình viên

Làm thế nào để gọi một API LLM?

10 phút đọc

Trả lời nhanh

Gửi một yêu cầu HTTPS POST với API key trong header và phần thân JSON nêu tên mô hình cùng danh sách tin nhắn. Phản hồi là JSON chứa văn bản được tạo ra và số token. Nhiều nhà cung cấp chấp nhận định dạng chat completions kiểu OpenAI, trong khi Anthropic và Gemini cũng có định dạng riêng. Hãy giữ key trên máy chủ của bạn và trả tiền theo token.

Mở Định dạng JSON

Điều gì xảy ra trong một lần gọi API

API LLM là một dịch vụ web. Chương trình của bạn gửi văn bản, máy chủ của nhà cung cấp chạy mô hình, và kết quả quay về dưới dạng JSON. Giữa các lần gọi không có bộ nhớ: mỗi yêu cầu phải chứa toàn bộ cuộc hội thoại mà mô hình cần thấy.

Bạn cần ba thứ: một tài khoản có API key, URL của endpoint, và ID mô hình từ danh sách mô hình của nhà cung cấp. Trong các ví dụ dưới đây, MODEL_ID là chỗ giữ chỗ. Tên mô hình thay đổi thường xuyên, nên hãy sao chép ID hiện hành từ tài liệu của nhà cung cấp thay vì từ một bài blog.

Yêu cầu tối giản bằng curl, Python và JavaScript

Định dạng chat completions, do OpenAI công bố đầu tiên, là dạng yêu cầu được bắt chước rộng rãi nhất. Nhiều nhà cung cấp khác và máy chủ cục bộ chấp nhận nó, đôi khi chỉ cần đổi base URL. Đây là yêu cầu nhỏ nhất mà vẫn hữu ích:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "MODEL_ID",
    "messages": [
      {"role": "system", "content": "You answer in one short paragraph."},
      {"role": "user", "content": "What is a context window?"}
    ]
  }'

Danh sách messages chứa cuộc hội thoại. Mỗi tin nhắn có role và content. Tin nhắn system đặt quy tắc cho câu trả lời (các mô hình mới hơn của OpenAI cũng chấp nhận tên developer cho vai trò này). Tin nhắn user là yêu cầu, còn tin nhắn assistant là các câu trả lời trước đó, bạn gửi lại để tiếp tục cuộc hội thoại. Một phản hồi rút gọn trông như sau:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "A context window is ..."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 27, "completion_tokens": 64, "total_tokens": 91}
}

Văn bản nằm trong choices[0].message.content. Đối tượng usage đếm số token bạn bị tính phí. finish_reason cho biết vì sao mô hình dừng: stop nghĩa là nó đã viết xong, còn length nghĩa là nó chạm giới hạn token của bạn và câu trả lời bị cắt.

Trong Python, cùng lời gọi đó với timeout và thử lại trông như sau. Nó dùng thư viện requests phổ biến. SDK chính thức của từng nhà cung cấp làm cùng việc này và tự thêm cơ chế thử lại cho bạn.

import os, random, time
import requests

URL = "https://api.openai.com/v1/chat/completions"
API_KEY = os.environ["OPENAI_API_KEY"]   # read from the server environment

def chat(messages, model="MODEL_ID", max_attempts=5):
    body = {"model": model, "messages": messages,
            "max_completion_tokens": 500}   # many compatible APIs: "max_tokens"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    for attempt in range(max_attempts):
        last = attempt == max_attempts - 1
        try:
            r = requests.post(URL, headers=headers, json=body, timeout=60)
        except (requests.Timeout, requests.ConnectionError):
            if last:
                raise
            time.sleep(2 ** attempt + random.random())   # network error: retry
            continue
        if r.status_code == 429 or r.status_code >= 500:
            if last:
                break
            backoff = 2 ** attempt + random.random()      # exponential + jitter
            try:
                wait = max(float(r.headers.get("Retry-After", 0)), backoff)
            except ValueError:
                wait = backoff
            time.sleep(wait)
            continue
        r.raise_for_status()                # 400, 401, 403: fix the request, do not retry
        data = r.json()
        return data["choices"][0]["message"]["content"], data["usage"]
    raise RuntimeError("API still failing after retries")

text, usage = chat([{"role": "user", "content": "What is a context window?"}])
print(text)
print(usage["prompt_tokens"], "in /", usage["completion_tokens"], "out")

Các nhà cung cấp khác nhau: tương thích OpenAI, Anthropic và Gemini

Dạng chat completions phổ biến nhưng không phải phổ quát. Messages API của Anthropic và Gemini API của Google có định dạng gốc riêng. Cả hai nhà cung cấp cũng công bố tùy chọn tương thích, nhưng API gốc của họ phơi bày mọi tính năng trước. Những khác biệt chính:

MụcChat completions kiểu OpenAIAnthropic Messages APIGemini API (gốc)
EndpointPOST /v1/chat/completionsPOST /v1/messagesPOST /v1beta/models/{model}:generateContent
Header API keyAuthorization: Bearer <key>x-api-key cùng header ngày bắt buộc anthropic-versionx-goog-api-key
Chỉ dẫn hệ thốngTin nhắn system hoặc developerTrường system ở cấp cao nhấtTrường systemInstruction ở cấp cao nhất
Trường bắt buộcmodel, messagesmodel, max_tokens, messagescontents (mô hình nằm trong URL)
Văn bản trả lờichoices[0].message.contentCác khối văn bản trong mảng contentcandidates[0].content.parts[].text
Streaming"stream": true, server-sent events kết thúc bằng [DONE]"stream": true, server-sent events có tênPhương thức streamGenerateContent với alt=sse

Gemini có base URL tương thích OpenAI, và nhiều nhà cung cấp khác, gồm DeepSeek và một số nơi lưu trữ mô hình Trung Quốc và mô hình mở, chấp nhận định dạng chat completions. Mức tương thích thường chỉ một phần: các tính năng nâng cao như đầu ra có cấu trúc, gọi công cụ hay đầu vào ảnh có thể khác nhau. Hãy đọc trang tương thích của từng nhà cung cấp trước khi chuyển chỉ bằng việc đổi URL.

Giữ API key trên máy chủ

Ai có API key của bạn đều có thể tiêu tiền của bạn. Đừng bao giờ đặt nó trong JavaScript của trình duyệt, ứng dụng di động, kho mã công khai hay file môi trường front-end bị đóng gói vào trang. Tất cả những chỗ này đều đọc được bởi người dùng.

  1. Lưu key trong biến môi trường hoặc trình quản lý bí mật trên máy chủ của bạn.
  2. Cho ứng dụng web hoặc di động gọi endpoint backend của chính bạn.
  3. Ở backend, kiểm tra người dùng (đăng nhập, giới hạn tốc độ theo người dùng, độ dài đầu vào), rồi gọi API LLM bằng key.
  4. Chỉ trả kết quả về cho client.
  5. Đặt hạn mức chi tiêu hằng tháng trong bảng điều khiển của nhà cung cấp, dùng key riêng cho từng dự án, và thay key ngay khi bị lộ.

Streaming: hiển thị câu trả lời khi nó đang được viết

Một câu trả lời dài có thể mất nhiều giây. Với "stream": true, máy chủ gửi từng mẩu nhỏ dưới dạng server-sent events, nên giao diện của bạn hiển thị văn bản ngay khi nó xuất hiện. Mỗi dòng sự kiện bắt đầu bằng data: và chứa một khối với delta. Định dạng OpenAI kết thúc bằng data: [DONE]. Ví dụ này chạy trên máy chủ Node.js:

// Node.js 18+ (server side). Prints the reply as it arrives.
const res = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: "MODEL_ID",
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about rain." }],
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);

const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of res.body) {
  buffer += decoder.decode(chunk, { stream: true });
  const lines = buffer.split("\n");
  buffer = lines.pop();                       // keep a partial line for later
  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6);
    if (data === "[DONE]") continue;          // end of stream
    const delta = JSON.parse(data).choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
  }
}

Để hiển thị luồng trong trình duyệt, hãy chuyển tiếp các khối từ máy chủ của bạn tới trang. Đừng gọi nhà cung cấp từ trình duyệt bằng key của bạn. Trong định dạng OpenAI, usage không có trong các khối streaming trừ khi bạn đặt stream_options với include_usage, vì vậy hãy bật nó nếu bạn cần số token để tính phí.

Nhận JSON thay vì văn xuôi

Khi mã sẽ đọc câu trả lời, hãy yêu cầu JSON. Có hai mức. Chế độ JSON ("response_format": {"type": "json_object"}) đảm bảo JSON hợp lệ, nhưng không đảm bảo các trường nào. Đầu ra có cấu trúc với JSON Schema khiến mô hình tuân theo đúng các trường và kiểu của bạn, trên những mô hình hỗ trợ:

{
  "model": "MODEL_ID",
  "messages": [
    {"role": "user", "content": "Extract the total and currency: 'Amount due: 1.250,00 EUR'"}
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "invoice_total",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "total": {"type": "number"},
          "currency": {"type": "string"}
        },
        "required": ["total", "currency"],
        "additionalProperties": false
      }
    }
  }
}

Ngay cả khi có schema, hãy kiểm tra lại kết quả đã phân tích trong mã của bạn. Giá trị vẫn có thể sai, chẳng hạn một con số đọc nhầm, dù cấu trúc đúng. Anthropic và Gemini hỗ trợ schema và định nghĩa công cụ theo định dạng riêng của họ. Khi gỡ lỗi các phản hồi JSON dài, một trình định dạng như công cụ json của cv.cm giúp đọc đầu ra lồng nhau dễ hơn.

Giới hạn tốc độ, thử lại và lỗi

Nhà cung cấp giới hạn cả số yêu cầu mỗi phút (RPM) lẫn số token mỗi phút (TPM), và giới hạn nào chạm trước thì áp dụng. Khi vượt giới hạn, bạn nhận mã trạng thái HTTP 429. Hãy xử lý lỗi theo loại:

Trạng tháiÝ nghĩaCần làm
400Yêu cầu sai: trường sai, quá nhiều tokenSửa yêu cầu. Không thử lại.
401 / 403Key thiếu, sai hoặc không được phépKiểm tra key và quyền. Không thử lại.
429Giới hạn tốc độ, hoặc đôi khi hết hạn mứcChờ và thử lại với backoff. Nếu thông báo nói về hạn mức hoặc thanh toán, hãy dừng và xử lý tài khoản.
500 / 503Lỗi máy chủ hoặc quá tảiThử lại với backoff.
Hết thời gianKhông có phản hồi kịp lúcThử lại một hoặc hai lần. Dùng streaming cho câu trả lời dài.

Thử lại với exponential backoff: chờ khoảng 1 giây, rồi 2, 4, 8, cộng thêm một độ trễ ngẫu nhiên nhỏ (jitter) để nhiều client không thử lại cùng một lúc. Nếu phản hồi có header Retry-After, hãy chờ ít nhất bằng thời gian đó. Giới hạn số lần thử. Yêu cầu thất bại cũng có thể được tính vào giới hạn của bạn, nên thử lại ngay lập tức trong vòng lặp dày chỉ làm mọi thứ tệ hơn. Nếu bạn dùng SDK chính thức, hãy nhớ rằng nó đã tự thử lại một số lỗi.

Chi phí được tính bằng token

Bạn trả tiền cho token đầu vào (mọi thứ bạn gửi, gồm system prompt và toàn bộ lịch sử hội thoại) và token đầu ra (những gì mô hình viết). Token đầu ra thường được định giá cao hơn token đầu vào. Công thức rất đơn giản:

cost = input_tokens  / 1,000,000 * input_price_per_million
     + output_tokens / 1,000,000 * output_price_per_million

Example with made-up prices (2.00 in, 8.00 out per million tokens):
  27 input + 64 output tokens
  = 0.000054 + 0.000512 = 0.000566 per call
  x 100,000 calls a month = about 56.60
  • Đọc số token thực từ usage trong mỗi phản hồi và ghi log. Đừng ước tính từ số từ.
  • Giới hạn độ dài mỗi câu trả lời: max_completion_tokens ở OpenAI (max_tokens cũ hơn đã bị coi là lỗi thời ở đó), max_tokens ở Anthropic và nhiều API tương thích.
  • Lịch sử chat lớn dần sau mỗi lượt, nên tin nhắn thứ 20 tốn hơn tin nhắn đầu tiên. Hãy cắt bớt hoặc tóm tắt các lượt cũ.
  • Dùng mô hình nhỏ hơn cho tác vụ đơn giản như phân loại, và mô hình lớn hơn chỉ ở nơi chất lượng đòi hỏi.
  • Một số nhà cung cấp giảm giá cho phần đầu prompt lặp lại (prompt caching) hoặc có xử lý hàng loạt rẻ hơn cho các việc có thể chờ.

Câu hỏi thường gặp

Tôi có thể gọi API LLM trực tiếp từ trình duyệt không?

Không an toàn nếu dùng key của bạn, vì mọi khách truy cập đều đọc được nó từ trang. Hãy gọi máy chủ của bạn, nơi sẽ thêm key. Ngoại lệ là key do người dùng tự nhập cho tài khoản của họ, hoặc mô hình chạy hoàn toàn trong trình duyệt.

Vì sao API quên những gì tôi nói trước đó?

API không giữ trạng thái. Mỗi yêu cầu phải chứa các tin nhắn trước đó mà bạn muốn mô hình thấy. Ứng dụng chat lưu lịch sử và gửi lại mỗi lần.

Temperature làm gì, và tôi có nên đặt nó không?

Temperature điều khiển mức ngẫu nhiên khi chọn từ: thấp hơn thì dễ đoán, cao hơn thì đa dạng. Một số nhà cung cấp và mô hình mới hạn chế hoặc bỏ các tham số lấy mẫu, nên hãy xem tài liệu tham chiếu API trước khi đặt.

Vì sao câu trả lời của tôi dừng giữa chừng?

Câu trả lời đã chạm giới hạn token bạn đặt, hoặc mức đầu ra tối đa của mô hình. Hãy kiểm tra finish_reason (hoặc stop_reason ở Anthropic). Tăng giới hạn hoặc yêu cầu câu trả lời ngắn hơn.

Có API LLM miễn phí không?

Một số nhà cung cấp có gói miễn phí hoặc tín dụng dùng thử với giới hạn tốc độ thấp, và mô hình mở có thể chạy trên máy của bạn sau một máy chủ cục bộ tương thích OpenAI. Gói miễn phí và điều khoản của chúng thay đổi thường xuyên, nên hãy xem trang hiện hành của nhà cung cấp.

Tất cả hướng dẫn AI