Hướng dẫn sử dụng Gemini API cho Developer
Tài liệu chi tiết Gemini API: endpoint, xác thực, cấu trúc request/response, các tham số generationConfig, truyền ảnh (inline & Files API), JSON mode, streaming, function calling, safety, đếm token, xử lý lỗi và tích hợp Laravel
Hướng dẫn sử dụng Gemini API cho Developer
Bài này đi thẳng vào REST API
generateContent— nền tảng chung cho mọi SDK (Python, Node, Go, Java) lẫn khi bạn tự gọi bằngcurl/PHP/HTTP client. Hiểu REST rồi thì mọi SDK chỉ là lớp bọc mỏng bên trên.
1. Lấy API key
- Vào Google AI Studio → https://aistudio.google.com/apikey.
- Bấm Create API key, chọn project.
- Copy key dạng
AIza....
⚠️ Không bao giờ để key ở frontend (JS trình duyệt, app mobile). Key phải nằm ở backend và gọi thay cho client. Lộ key = người khác xài hết quota, tính tiền vào bạn.
Đặt key vào biến môi trường:
export GEMINI_API_KEY="AIza..."
2. Endpoint & xác thực
Base URL của Gemini API (bản public, không phải Vertex AI):
https://generativelanguage.googleapis.com/v1beta/
Endpoint sinh nội dung:
POST https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent
Có 2 cách truyền key:
# Cách 1: query param ?key=
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$GEMINI_API_KEY"
# Cách 2 (khuyến nghị): header x-goog-api-key — key không lọt vào log URL
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY"
Chọn model
| Model | Dùng khi |
|---|---|
gemini-2.5-flash |
Mặc định tốt nhất về giá/tốc độ. Đa số việc chọn cái này. |
gemini-2.5-flash-lite |
Rẻ & nhanh nhất, tác vụ đơn giản, volume lớn. |
gemini-2.5-pro |
Suy luận sâu, code khó, bài toán phức tạp. |
gemini-2.0-flash |
Đời trước, vẫn ổn định. |
Họ model đổi liên tục (đã có các bản
3.x). Liệt kê model hiện có:GET /v1beta/models(kèm headerx-goog-api-key).
3. Request tối giản
Gọi text đơn giản nhất:
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"contents": [
{ "parts": [ { "text": "Viết một câu chào bằng tiếng Việt." } ] }
]
}'
contents là bắt buộc. Mọi thứ khác là tuỳ chọn.
4. Cấu trúc request body đầy đủ
{
// BẮT BUỘC. Lịch sử hội thoại + câu hỏi mới nhất.
"contents": [
{
"role": "user", // "user" hoặc "model" (câu trả lời của AI)
"parts": [ // 1 message có thể gồm nhiều "part"
{ "text": "..." }, // phần text
{ "inline_data": { "mime_type": "...", "data": "..." } }, // file base64
{ "file_data": { "mime_type": "...", "file_uri": "..." } } // file qua Files API
]
}
],
// Chỉ dẫn hệ thống (persona, quy tắc). Chỉ nhận text.
"systemInstruction": {
"parts": [ { "text": "Bạn là trợ lý trả lời ngắn gọn bằng tiếng Việt." } ]
},
// Điều chỉnh cách sinh output (xem mục 5).
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 1024
},
// Ngưỡng chặn nội dung độc hại (xem mục 12).
"safetySettings": [
{ "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE" }
],
// Công cụ AI có thể gọi: function calling / code execution (xem mục 11).
"tools": [ /* ... */ ],
// Ngữ cảnh đã cache (tiết kiệm token cho prompt lặp lại nhiều).
"cachedContent": "cachedContents/abc123"
}
Các trường chính
| Trường | Kiểu | Ý nghĩa |
|---|---|---|
contents[] |
array | Bắt buộc. Nội dung hội thoại. Single-turn = 1 phần tử; multi-turn = cả lịch sử. |
contents[].role |
string | user (bạn) hoặc model (AI). Bỏ qua được khi single-turn. |
contents[].parts[] |
array | Các mảnh nội dung: text, inline_data, file_data, functionCall, functionResponse. |
systemInstruction |
object | Định hướng hành vi model, tách khỏi câu hỏi user. |
generationConfig |
object | Tinh chỉnh output (nhiệt độ, độ dài, định dạng...). |
safetySettings[] |
array | Bật/tắt bộ lọc an toàn theo từng category. |
tools[] |
array | Khai báo function/code execution model được dùng. |
cachedContent |
string | Tên context đã cache để tái dùng, giảm chi phí. |
5. Tham số generationConfig
Đây là phần Dev cần nắm kỹ nhất — nó quyết định chất lượng & hình dạng output.
| Tham số | Kiểu | Mặc định | Tác dụng |
|---|---|---|---|
temperature |
float 0.0–2.0 |
~1.0 | Độ "sáng tạo". 0 = ổn định, lặp lại được; cao = đa dạng, ngẫu nhiên hơn. Task chính xác (trích xuất, phân loại) → để thấp (0–0.3). |
topP |
float 0–1 |
0.95 | Nucleus sampling: chỉ lấy các token cộng dồn xác suất ≤ topP. Giảm để output tập trung hơn. |
topK |
int | — | Chỉ xét K token khả dĩ nhất mỗi bước. |
maxOutputTokens |
int | tuỳ model | Giới hạn độ dài output. Đặt để tránh câu trả lời quá dài & kiểm soát chi phí. |
candidateCount |
int | 1 | Số phương án trả lời sinh ra (thường để 1). |
stopSequences[] |
string[] | — | Gặp chuỗi này thì dừng sinh. VD ["\n\n", "END"]. |
responseMimeType |
string | text/plain |
application/json để bật JSON Mode (mục 9), hoặc text/x.enum. |
responseSchema |
object | — | JSON Schema ép output đúng cấu trúc (đi kèm responseMimeType: application/json). |
presencePenalty |
float | — | Phạt token đã xuất hiện → khuyến khích chủ đề mới. |
frequencyPenalty |
float | — | Phạt theo tần suất lặp → giảm lặp từ. |
seed |
int | — | Cố định seed để tái lập kết quả (cùng input → cùng output, khi temperature thấp). |
thinkingConfig |
object | — | Với model 2.5+: {"thinkingBudget": 0} để tắt suy nghĩ (nhanh/rẻ hơn), hoặc số token dành cho suy luận. includeThoughts: true để trả kèm tóm tắt suy nghĩ. |
responseModalities[] |
string[] | ["TEXT"] |
Loại output mong muốn (một số model hỗ trợ ảnh/âm thanh). |
Ví dụ cấu hình cho tác vụ trích xuất dữ liệu ổn định:
"generationConfig": {
"temperature": 0.1,
"topP": 0.8,
"maxOutputTokens": 512,
"responseMimeType": "application/json"
}
6. Cấu trúc kết quả trả về
Response mẫu (đã lược):
{
"candidates": [
{
"content": {
"role": "model",
"parts": [ { "text": "Xin chào! Rất vui được gặp bạn." } ]
},
"finishReason": "STOP",
"index": 0,
"safetyRatings": [
{ "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE" }
]
}
],
"promptFeedback": {
"safetyRatings": [ /* ... */ ]
},
"usageMetadata": {
"promptTokenCount": 8,
"candidatesTokenCount": 12,
"totalTokenCount": 20
},
"modelVersion": "gemini-2.5-flash"
}
Ý nghĩa các trường
| Trường | Ý nghĩa |
|---|---|
candidates[] |
Danh sách phương án trả lời (thường 1). |
candidates[].content.parts[].text |
Text bạn cần lấy. Nối các parts lại nếu có nhiều. |
candidates[].finishReason |
Lý do dừng: STOP (xong bình thường), MAX_TOKENS (chạm giới hạn — output bị cắt!), SAFETY (bị chặn), RECITATION (nghi trích nguồn có bản quyền), OTHER. |
candidates[].safetyRatings[] |
Đánh giá an toàn của câu trả lời. |
promptFeedback |
Phản hồi về prompt đầu vào. Nếu prompt bị chặn, có promptFeedback.blockReason và không có candidates. |
usageMetadata.promptTokenCount |
Token của input (tính tiền). |
usageMetadata.candidatesTokenCount |
Token của output (tính tiền). |
usageMetadata.thoughtsTokenCount |
Token cho "suy nghĩ" (model 2.5+, nếu có). |
usageMetadata.totalTokenCount |
Tổng token của request này. |
modelVersion |
Phiên bản model thực tế đã phục vụ. |
Lấy text nhanh bằng jq:
... | jq -r '.candidates[0].content.parts[0].text'
🔎 Luôn kiểm tra
finishReason. Nếu làMAX_TOKENS→ output bị cắt giữa chừng, cần tăngmaxOutputTokens. NếuSAFETY/promptFeedback.blockReason→ nội dung bị bộ lọc chặn.
7. Truyền ảnh (và file khác)
Có 2 cách đưa ảnh vào prompt.
Cách A — inline_data (base64), cho file nhỏ
Dùng khi tổng dung lượng request < 20MB. Ảnh được nhúng base64 ngay trong JSON.
# Encode ảnh sang base64
B64=$(base64 -w0 anh.jpg) # Linux; macOS: base64 -i anh.jpg
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"contents": [{
"parts": [
{ "text": "Mô tả chi tiết bức ảnh này." },
{ "inline_data": { "mime_type": "image/jpeg", "data": "'"$B64"'" } }
]
}]
}'
Điểm quan trọng:
mime_typephải khớp file thật:image/png,image/jpeg,image/webp,image/heic,image/heif.datalà chuỗi base64 không có tiền tốdata:image/...;base64,.- Đặt phần
text(câu hỏi) cùng message với ảnh.
Cách B — Files API, cho file lớn / dùng lại nhiều lần
Dùng khi file > 20MB, hoặc muốn hỏi nhiều lần trên cùng file (upload 1 lần, tham chiếu bằng URI; file lưu ~48 giờ).
# B1: Upload
NUM_BYTES=$(wc -c < anh.jpg)
curl "https://generativelanguage.googleapis.com/upload/v1beta/files" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-D headers.tmp \
-H "X-Goog-Upload-Protocol: resumable" \
-H "X-Goog-Upload-Command: start" \
-H "X-Goog-Upload-Header-Content-Length: $NUM_BYTES" \
-H "X-Goog-Upload-Header-Content-Type: image/jpeg" \
-H "Content-Type: application/json" \
-d '{"file": {"display_name": "anh"}}'
UPLOAD_URL=$(grep -i "x-goog-upload-url: " headers.tmp | cut -d" " -f2 | tr -d "\r")
# B2: Đẩy bytes
curl "$UPLOAD_URL" \
-H "Content-Length: $NUM_BYTES" \
-H "X-Goog-Upload-Offset: 0" \
-H "X-Goog-Upload-Command: upload, finalize" \
--data-binary "@anh.jpg" > file.json
FILE_URI=$(jq -r '.file.uri' file.json)
# B3: Dùng file_uri trong generateContent
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"contents": [{
"parts": [
{ "text": "Trong ảnh có những vật gì?" },
{ "file_data": { "mime_type": "image/jpeg", "file_uri": "'"$FILE_URI"'" } }
]
}]
}'
Cùng cơ chế này áp dụng cho audio (
audio/mpeg...), video (video/mp4) và PDF (application/pdf). Video/audio lớn phải poll tới khifile.state == "ACTIVE"mới gọi được.
Nhiều ảnh trong 1 lần hỏi
Chỉ cần thêm nhiều part:
"parts": [
{ "text": "So sánh hai ảnh này khác nhau chỗ nào." },
{ "inline_data": { "mime_type": "image/png", "data": "<base64_1>" } },
{ "inline_data": { "mime_type": "image/png", "data": "<base64_2>" } }
]
8. Hội thoại nhiều lượt (multi-turn)
Gemini không nhớ hội thoại giữa các request — bạn phải tự gửi lại toàn bộ
lịch sử mỗi lần, xen kẽ role: user và role: model:
{
"contents": [
{ "role": "user", "parts": [ { "text": "Thủ đô Việt Nam là gì?" } ] },
{ "role": "model", "parts": [ { "text": "Là Hà Nội." } ] },
{ "role": "user", "parts": [ { "text": "Dân số bao nhiêu?" } ] }
]
}
Lượt cuối (user) là câu hỏi mới; các lượt trước là bối cảnh. Nhớ append
câu trả lời của model (role: model) vào lịch sử trước khi hỏi tiếp.
9. JSON Mode — kết quả có cấu trúc
Ép model trả đúng JSON theo schema, khỏi phải regex/parse text lem nhem:
{
"contents": [
{ "parts": [ { "text": "Tách thông tin: 'Nguyễn Văn A, 28 tuổi, Hà Nội'." } ] }
],
"generationConfig": {
"responseMimeType": "application/json",
"responseSchema": {
"type": "object",
"properties": {
"ten": { "type": "string" },
"tuoi": { "type": "integer" },
"thanhpho": { "type": "string" }
},
"required": ["ten", "tuoi", "thanhpho"]
}
}
}
candidates[0].content.parts[0].text khi đó là một chuỗi JSON hợp lệ, chỉ
việc json_decode. Hỗ trợ type: string, integer, number, boolean,
array, object, và enum để giới hạn giá trị.
10. Streaming (SSE)
Muốn hiện chữ dần dần (kiểu ChatGPT) thay vì chờ trọn câu trả lời, dùng
streamGenerateContent với alt=sse:
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{ "contents": [ { "parts": [ { "text": "Kể một câu chuyện ngắn." } ] } ] }'
Server trả về nhiều dòng data: {...}, mỗi dòng là một mảnh (chunk) chứa
candidates[0].content.parts[0].text. Nối các mảnh lại thành câu trả lời đầy đủ.
11. Function calling
Cho model "gọi hàm" của bạn: khai báo hàm trong tools, model trả về ý định gọi
kèm tham số, bạn chạy hàm thật rồi gửi kết quả lại.
{
"contents": [ { "parts": [ { "text": "Thời tiết Hà Nội hôm nay?" } ] } ],
"tools": [{
"function_declarations": [{
"name": "get_weather",
"description": "Lấy thời tiết hiện tại theo thành phố",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}]
}]
}
Model trả candidates[0].content.parts[0].functionCall = { name, args }. Bạn gọi
API thời tiết thật, rồi gửi tiếp một message role: "function" chứa
functionResponse để model chốt câu trả lời tự nhiên.
12. Safety settings
Điều chỉnh mức chặn theo từng loại nội dung:
Categories: HARM_CATEGORY_HARASSMENT, HARM_CATEGORY_HATE_SPEECH,
HARM_CATEGORY_SEXUALLY_EXPLICIT, HARM_CATEGORY_DANGEROUS_CONTENT,
HARM_CATEGORY_CIVIC_INTEGRITY.
Thresholds (mức ngưỡng chặn):
| Threshold | Nghĩa |
|---|---|
BLOCK_NONE |
Không chặn gì. |
BLOCK_ONLY_HIGH |
Chỉ chặn khi xác suất độc hại cao. |
BLOCK_MEDIUM_AND_ABOVE |
Chặn từ mức trung bình (mặc định phổ biến). |
BLOCK_LOW_AND_ABOVE |
Chặn kể cả mức thấp (chặt nhất). |
"safetySettings": [
{ "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_ONLY_HIGH" },
{ "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE" }
]
Nếu bị chặn: response có finishReason: "SAFETY" hoặc promptFeedback.blockReason.
13. Đếm token & chi phí
Ước lượng token trước khi gửi bằng endpoint countTokens (miễn phí):
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:countTokens" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{ "contents": [ { "parts": [ { "text": "Đoạn văn cần đếm token..." } ] } ] }'
Sau mỗi lần gọi thật, đọc usageMetadata để biết đã tiêu bao nhiêu token
(input + output). Chi phí = token × đơn giá theo model. Ảnh/video cũng quy đổi ra
token, nên file càng lớn/độ phân giải cao → càng tốn.
14. Xử lý lỗi
Lỗi trả về dạng:
{
"error": {
"code": 400,
"message": "API key not valid. Please pass a valid API key.",
"status": "INVALID_ARGUMENT"
}
}
| HTTP | status |
Nguyên nhân & cách xử lý |
|---|---|---|
400 |
INVALID_ARGUMENT |
Body sai (thiếu contents, mime sai, schema lỗi). Kiểm tra JSON. |
400 |
FAILED_PRECONDITION |
Vùng/khu vực chưa hỗ trợ tier miễn phí, hoặc cần bật billing. |
403 |
PERMISSION_DENIED |
Key sai/không có quyền cho model này. |
404 |
NOT_FOUND |
Sai tên model, hoặc file_uri đã hết hạn. |
429 |
RESOURCE_EXHAUSTED |
Vượt rate limit/quota. Chờ rồi thử lại (exponential backoff). |
500 |
INTERNAL |
Lỗi phía Google. Retry. |
503 |
UNAVAILABLE |
Quá tải tạm thời. Retry với backoff. |
504 |
DEADLINE_EXCEEDED |
Prompt quá lớn/lâu. Rút gọn input hoặc tăng timeout. |
Chiến lược retry cho 429/500/503: thử lại 3–5 lần, giãn cách tăng dần
(1s → 2s → 4s → 8s) kèm jitter. Đừng retry 400/403 (lỗi do bạn, retry vô ích).
15. Tích hợp trong Laravel
Dự án Laravel có sẵn chỗ cấu hình trong config/services.php:
'google' => [
'key' => env('GOOGLE_API_KEY'),
'gemini' => [
'base_url' => 'https://generativelanguage.googleapis.com/v1beta/',
'model' => 'gemini-2.5-flash',
],
],
.env:
GOOGLE_API_KEY=AIza...
Service gọi API bằng HTTP client tích hợp sẵn của Laravel:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class GeminiService
{
private string $baseUrl;
private string $model;
private string $key;
public function __construct()
{
$this->baseUrl = rtrim(config('services.google.gemini.base_url'), '/');
$this->model = config('services.google.gemini.model');
$this->key = (string) config('services.google.key');
}
/** Sinh text từ 1 prompt. */
public function ask(string $prompt): string
{
$res = Http::withHeaders(['x-goog-api-key' => $this->key])
->timeout(30)
->retry(3, 1000, throw: false) // retry 429/5xx
->post("{$this->baseUrl}/models/{$this->model}:generateContent", [
'contents' => [
['parts' => [['text' => $prompt]]],
],
'generationConfig' => [
'temperature' => 0.4,
'maxOutputTokens' => 1024,
],
]);
if ($res->failed()) {
throw new RuntimeException(
'Gemini error: '.$res->json('error.message', 'unknown')
);
}
return (string) $res->json('candidates.0.content.parts.0.text', '');
}
/** Hỏi kèm ảnh (đường dẫn file cục bộ). */
public function askWithImage(string $prompt, string $imagePath, string $mime = 'image/jpeg'): string
{
$base64 = base64_encode(file_get_contents($imagePath));
$res = Http::withHeaders(['x-goog-api-key' => $this->key])
->timeout(60)
->post("{$this->baseUrl}/models/{$this->model}:generateContent", [
'contents' => [[
'parts' => [
['text' => $prompt],
['inline_data' => ['mime_type' => $mime, 'data' => $base64]],
],
]],
]);
return (string) $res->json('candidates.0.content.parts.0.text', '');
}
}
Dùng:
$gemini = app(App\Services\GeminiService::class);
echo $gemini->ask('Tóm tắt Laravel trong 2 câu.');
echo $gemini->askWithImage('Ảnh này là gì?', storage_path('app/anh.jpg'));
16. Best practices
- 🔐 Key ở backend, luôn luôn. Frontend gọi backend của bạn, backend mới gọi Gemini.
- 🎯 Chọn model theo việc —
flashđủ cho ~80% tác vụ, rẻ hơnpronhiều. - 🧊
temperaturethấp cho tác vụ chính xác (trích xuất, phân loại, JSON); cao cho sáng tạo. - 🔁 Retry có backoff cho
429/500/503; không retry400/403. - 🧾 Luôn check
finishReason—MAX_TOKENSnghĩa là output bị cắt. - 🗂️ Files API cho file lớn/dùng lại; inline base64 cho file nhỏ 1 lần.
- ⚡ Cache prompt lặp lại (
cachedContent) khi gửi cùng khối ngữ cảnh dài nhiều lần.