Hướng dẫn sử dụng Gemini API cho Developer

Nguyễn Dương Thế Vĩ · Created: 11:49 13/09/2026 · Updated: 13:36 19/09/2026 · 26 Views

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ằng curl/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

  1. Vào Google AI Studiohttps://aistudio.google.com/apikey.
  2. Bấm Create API key, chọn project.
  3. 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

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 header x-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." } ] }
    ]
  }'

contentsbắ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.blockReasonkhô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ăng maxOutputTokens. Nếu SAFETY/promptFeedback.blockReason → nội dung bị bộ lọc chặn.


7. Truyền ảnh (và file khá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_type phải khớp file thật: image/png, image/jpeg, image/webp, image/heic, image/heif.
  • data là 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 khi file.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: userrole: 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ệcflash đủ cho ~80% tác vụ, rẻ hơn pro nhiều.
  • 🧊 temperature thấ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 retry 400/403.
  • 🧾 Luôn check finishReasonMAX_TOKENS nghĩ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.