Bỏ qua để đến nội dung

API cho hình ảnh và video

Sau bài này, bạn sẽ:

  • Gửi được một tấm ảnh cho mô hình nhìn được ảnh (vision): đặt ảnh vào messages dưới dạng data URL base64, cạnh câu hỏi bằng chữ.
  • Sinh được ảnh từ prompt bằng images.generate, đọc kết quả b64_json và lưu ra tệp.
  • Giải thích được vì sao video là một tác vụ bất đồng bộ (asynchronous job): gửi yêu cầu, giữ id, hỏi lại trạng thái bằng polling, tải kết quả về kịp lúc.
  • Nhận ra các giới hạn quanh ảnh và video do AI tạo: kiểm duyệt nội dung (content moderation), bản quyền, và vì sao dấu nguồn gốc như C2PA không phải bằng chứng ảnh thật hay giả.

Bạn đã biết gửi chữ cho mô hình và nhận chữ về. Giờ có ba yêu cầu mới:

  1. Chụp một tờ hóa đơn, hỏi mô hình “tổng tiền là bao nhiêu?”.
  2. Nhờ mô hình vẽ một bức tranh màu nước cho bài blog.
  3. Nhờ mô hình làm một đoạn video tám giây.

Nghe thì giống nhau: gửi yêu cầu, nhận kết quả. Nhưng ở tầng API, đó là ba con đường khác nhau.

Hỏi về ảnh vẫn là một cuộc chat. Bạn vẫn gọi chat.completions, chỉ khác là tin nhắn của bạn giờ gồm hai phần: một câu hỏi và một tấm ảnh. Mô hình trả lời bằng chữ, như mọi lần.

Sinh ảnh đi qua một cánh cửa khác: images.generate. Vào là prompt, ra là byte của một tấm ảnh. Không có messages, không có lượt hội thoại. Nhiều người tưởng chỉ một API làm được cả hai việc, rồi bối rối khi mô hình chat trả về một đoạn mô tả thay vì một bức tranh.

Video thì lâu: một lần dựng có thể mất vài phút. Không ai giữ kết nối mở chừng ấy thời gian. Nó giống gửi bưu kiện: bạn gửi đơn, nhận mã vận đơn, thỉnh thoảng tra xem hàng tới đâu. Hàng tới kho thì phải lấy ngay, vì kho chỉ giữ một thời gian ngắn.

Bài toán của bài này là: với SDK openai quen thuộc, gửi được ảnh cho mô hình đọc, xin được ảnh mới từ prompt, và hiểu cách chờ một kết quả dài như video, kèm các giới hạn về an toàn và nguồn gốc của nội dung do AI tạo.

Ở bài trước, content của một lượt là một chuỗi. Muốn gửi ảnh, content của lượt user trở thành danh sách các phần (content parts). Mỗi phần có type:

{
"role": "user",
"content": [
{ "type": "text", "text": "Ảnh có những hình gì, màu gì, nằm ở đâu?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } }
]
}

Một lượt có thể trộn nhiều phần chữ và nhiều ảnh. Mô hình đọc chúng theo thứ tự rồi trả lời bằng chữ: lượt assistant vẫn có content là chuỗi như bài trước.

Trường url nhận hai kiểu giá trị. Tên trường là image_url, nhưng url không bắt buộc là link web:

  • Link web (https://...): máy chủ của nhà cung cấp tự tải ảnh về. Ảnh phải công khai.
  • Data URL: một “địa chỉ” chứa luôn dữ liệu của ảnh, dạng data:<kiểu MIME>;base64,<dữ liệu>. Ví dụ data:image/png;base64,iVBORw0KGgo.... Kiểu MIME (image/png, image/jpeg…) phải khớp định dạng thật của tệp.

Theo tài liệu của OpenAI, Gemini, Groq và Ollama (kiểm tra 10/2026), cả bốn nơi đều nhận data URL base64, còn Ollama không nhận link web. Vì vậy code của bài dùng data URL: đó là cách chạy được ở nhiều nơi nhất.

Base64 làm chuỗi dài thêm khoảng một phần ba. Base64 là cách viết dữ liệu nhị phân bằng 64 ký tự an toàn cho văn bản (A–Z, a–z, 0–9, +, /). Mỗi ký tự mang 6 bit. Ba byte là 24 bit, vừa đúng bốn ký tự. Nhóm cuối thiếu byte thì được đệm bằng dấu = cho đủ bốn ký tự. Nên một tệp ss byte thành chuỗi dài:

4⌈s3⌉4\left\lceil \frac{s}{3} \right\rceil

ký tự, trong đó ⌈⋅⌉\lceil\cdot\rceil là làm tròn lên. Khi ss lớn, tỉ lệ này gần 4/3≈1.334/3 \approx 1.33: một ảnh 3 MB thành chuỗi khoảng 4 MB trong yêu cầu gửi đi. Ảnh lớn nên thu nhỏ trước khi gửi. Mỗi nhà cung cấp còn giới hạn kích thước và số ảnh trong một yêu cầu, mỗi nơi một khác, nên hãy xem tài liệu của nơi bạn dùng.

Ảnh cũng tốn token. Mô hình không đọc chuỗi base64 như đọc chữ. Nhà cung cấp giải mã ảnh, rồi biến nó thành một chuỗi token ảnh cho mô hình. Ảnh càng lớn, càng chi tiết thì càng nhiều token, và bạn trả tiền cho số token đó. Riêng OpenAI có thêm trường tùy chọn detail trong image_url ("low", "high", "auto"…; giá trị được phép tùy mô hình): "low" rẻ hơn, đủ khi chỉ cần ý chung của ảnh. Vì sao mô hình “nhìn” được ảnh là chuyện của bài Multimodal.

Mô hình phải nhìn được ảnh. Không phải mô hình chat nào cũng nhận ảnh. Gửi ảnh cho mô hình chỉ đọc chữ thì bạn nhận lỗi 400, hoặc tệ hơn, một câu trả lời bịa. Biến LLM_MODEL của bài này phải là một mô hình có hỗ trợ ảnh (vision) ở nhà cung cấp bạn dùng.

Sinh ảnh dùng một endpoint riêng, không qua chat:

response = client.images.generate(
model=IMAGE_MODEL,
prompt=prompt,
size="1024x1024",
n=1,
)
  • model: mô hình sinh ảnh. Nó khác mô hình chat, nên code đọc tên từ một biến riêng là IMAGE_MODEL, theo quy ước <KHẢ_NĂNG>_MODEL của phần này.
  • prompt: mô tả bức ảnh bạn muốn. Cách viết prompt tốt là chủ đề của bài Prompt Engineering.
  • size: kích thước, ví dụ "1024x1024". n: số ảnh muốn nhận.

Kết quả nằm trong response.data, mỗi phần tử là một ảnh. Ảnh thường về dưới dạng b64_json: chuỗi base64, giải mã bằng base64.b64decode là ra byte của tệp ảnh. Có nơi trả url thay vào đó: một link tạm, sẽ hết hạn, nên phải tải về sớm. Code an toàn nhất là xem cả hai trường, vì trường không dùng sẽ là None.

Không phải nhà cung cấp nào cũng sinh ảnh. Theo tài liệu kiểm tra 10/2026:

  • OpenAI có images.generate, với nhiều tham số riêng như quality, output_format, background. Với các mô hình ảnh GPT, ảnh trả về dạng base64.
  • Gemini (qua lớp tương thích OpenAI) có images.generate, nhưng chỉ nhận prompt, model, n, size, response_format. Tham số khác bị bỏ qua im lặng, không báo lỗi. Tham số riêng của Gemini, như tỉ lệ khung hình, đi qua extra_body.
  • Groq và Ollama: trang tương thích OpenAI của hai nơi này không có endpoint sinh ảnh (10/2026), nên ô sinh ảnh chưa chạy được qua đó. (Ollama có sinh ảnh thử nghiệm qua dòng lệnh, ngoài lớp tương thích OpenAI; xem bài viết của Ollama.)

Bảng nhà cung cấp và OPENAI_BASE_URL của từng nơi nằm ở bài Sử dụng API văn bản. Có thể bạn phải dùng một nhà cung cấp cho phần đọc ảnh và một nhà khác cho phần sinh ảnh.

Dựng một đoạn video có thể mất vài phút. Vì vậy API video không trả video ngay trong lần gọi. Nó theo khuôn tác vụ bất đồng bộ: gửi rồi chờ kết quả sau.

  1. Tạo job. Bạn gửi prompt. API trả về ngay một id và trạng thái, thường là queued (đang xếp hàng).
  2. Chờ. Trạng thái chuyển qua in_progress rồi tới completed hoặc failed. Bạn biết điều đó bằng một trong hai cách:
    • Polling: hỏi lại trạng thái theo chu kỳ.
    • Webhook: bạn đăng ký một địa chỉ web, nhà cung cấp tự gọi tới đó khi job xong. Bạn không phải hỏi, nhưng cần một máy chủ nhận được lời gọi từ bên ngoài.
  3. Lấy kết quả. Job xong thì tải file về và chép vào kho lưu trữ của bạn ngay, vì link tải thường chỉ sống ngắn hạn.

Polling viết bằng giả mã (không gắn với nhà cung cấp nào) trông như sau:

job = tạo_job(prompt) # trả về ngay: job.id, job.status = "queued"
chờ = 10 giây
lặp khi job.status chưa là "completed" hay "failed":
nếu đã quá thời hạn tối đa: báo hết giờ, dừng
ngủ(chờ)
job = hỏi_trạng_thái(job.id)
chờ = min(chờ × 2, 60 giây) # backoff: hỏi thưa dần
nếu job.status = "failed": báo lỗi, dừng
tải_về(job.id) rồi lưu vào kho của bạn ngay

Ba chi tiết đáng nhớ. Chờ thưa dần (backoff), giống cách retry ở bài trước, vì hỏi dồn dập chỉ tốn lượt gọi và dễ gặp lỗi 429. Luôn có thời hạn tối đa, để chương trình không chờ mãi một job bị kẹt. Và tải kết quả về ngay. Lấy ví dụ, tài liệu của API video OpenAI (API nay đã ngừng) gợi ý hỏi mỗi 10–20 giây, có backoff, hoặc dùng webhook; link tải chỉ sống tối đa một giờ.

Kiểm duyệt, bản quyền và nguồn gốc ảnh

Phần tiêu đề “Kiểm duyệt, bản quyền và nguồn gốc ảnh”

Kiểm duyệt nội dung. Các nhà cung cấp lọc cả prompt lẫn ảnh sinh ra. Ở OpenAI, yêu cầu bị chặn trả lỗi 400 với mã moderation_blocked, và tài liệu khuyên không thử lại khi chưa sửa prompt: gửi lại y nguyên thì kết quả vẫn vậy. SDK cũng không tự thử lại lỗi 400, nên code của bài bắt openai.BadRequestError và in mã lỗi. Bạn cũng có thể phải xác minh tổ chức trước khi dùng các mô hình GPT Image của OpenAI.

Bản quyền và quyền hình ảnh. Ai sở hữu ảnh do AI tạo, và dùng nó vào việc gì thì được, phụ thuộc điều khoản dịch vụ của từng nhà cung cấp và luật của từng nước. Bài này không phải lời khuyên pháp lý. Vài thói quen an toàn:

  • Đọc điều khoản dịch vụ của nhà cung cấp trước khi dùng ảnh vào việc thương mại, và hỏi người có chuyên môn khi cần.
  • Không đưa vào prompt hay ảnh đầu vào những thứ bạn không có quyền dùng: ảnh có bản quyền, mặt người thật chưa xin phép, nhãn hiệu.
  • Khi công bố, ghi rõ ảnh hay video do AI tạo.

Nguồn gốc nội dung (content provenance) trả lời câu hỏi “tấm ảnh này từ đâu ra?”. Có hai cách đánh dấu chính:

  • C2PA (Coalition for Content Provenance and Authenticity) là một chuẩn mở, gọi là Content Credentials: gắn vào tệp một tờ khai lịch sử, ghi ảnh được tạo và sửa bằng công cụ nào. Tờ khai nằm trong metadata (thông tin kèm theo tệp), nên dễ rơi mất khi chụp màn hình, cắt ghép, hay đăng lên mạng xã hội.
  • Watermark vô hình: một dấu in chìm ngay trong điểm ảnh. Nó khó xóa hơn metadata nhưng không tuyệt đối: chỉnh sửa mạnh vẫn có thể phá được. Ví dụ, Google ghi rằng mọi ảnh sinh ra bằng Gemini đều mang watermark SynthID.

Hai điều suy ra: không thấy dấu không chứng minh ảnh là thật, và có dấu chỉ nói ảnh được làm bằng công cụ nào, không bảo đảm nội dung đúng sự thật.

  • Tham số và định dạng khác nhau giữa các nhà cung cấp. Phần chung là messages có ảnh, và images.generate với vài tham số cơ bản. Mọi thứ khác phải đọc tài liệu từng nơi (hộp thứ ba ở mục Lỗi hay gặp).
  • Mô hình đọc ảnh vẫn có thể sai: đọc nhầm số trên hóa đơn, đếm sai đồ vật. Việc quan trọng thì kiểm lại kết quả.

Bảng đầu là hội thoại mà ô code “Hỏi mô hình về một tấm ảnh” gửi đi. Hãy nhìn vào lượt user: content của nó là một danh sách hai phần, không còn là một chuỗi như ở bài trước.

Vai tròNội dung
systemChuỗi: “Bạn trả lời ngắn gọn bằng tiếng Việt.”
userDanh sách hai phần. Phần 1, text: “Ảnh có những hình gì, màu gì, nằm ở đâu?”. Phần 2, image_url: data:image/png;base64,iVBORw0KGgo…, dài 22 ký tự tiền tố cộng 160 ký tự base64 của ảnh thử 118 byte
assistantMột chuỗi chữ: câu trả lời của mô hình. Mô hình đọc ảnh nhưng chỉ trả lời bằng chữ

Bảng thứ hai đặt ba luồng của bài cạnh nhau. Nhìn vào hai hàng “Gọi gì” và “Thời gian”: đó là chỗ ba luồng khác nhau nhiều nhất.

Tiêu chíHiểu ảnhSinh ảnhVideo
Gọi gìchat.completions.createimages.generateAPI video riêng của từng nhà (không có qua SDK openai, 10/2026)
Gửi đimessages, ảnh là một phần của contentprompt, size, nprompt, có khi kèm ảnh đầu vào
Nhận vềChữẢnh (b64_json hoặc url)Một id và trạng thái; file video tải sau
Thời gianNhư một lần chatThường lâu hơn chat, tùy mô hình và chất lượngVài phút; phải polling hoặc chờ webhook
Mô hìnhLLM_MODEL (phải nhìn được ảnh)IMAGE_MODELMô hình video

Code trong bài viết và kiểm tra lần cuối tháng 10/2026, với Python 3.14 và openai 3.37. Ô thứ nhất chạy ngay trên trang, vì nó chỉ dùng thư viện chuẩn của Python. Hai ô sau gọi API, nên bạn chạy trên máy mình. Cách đặt biến môi trường cho bash và PowerShell có ở mục Chuẩn bị: nhà cung cấp và biến môi trường của bài Sử dụng API văn bản; ô sinh ảnh cần thêm biến IMAGE_MODEL, đặt giống hệt LLM_MODEL.

Ô này tự vẽ một ảnh PNG nhỏ 48 × 48 điểm (nền trắng, hình vuông đỏ ở giữa) bằng thư viện chuẩn, rồi mã hóa base64 và ghép thành data URL, đúng như khi gửi ảnh cho API.

Từ byte ảnh tới data URL

# Biến byte của một ảnh PNG thành data URL base64, như khi gửi ảnh cho API.
import base64
import struct
import zlib
def demo_png(size):
# Ảnh thử: nền trắng, hình vuông đỏ ở giữa. Chỉ dùng thư viện chuẩn.
a, b = size // 4, 3 * size // 4
raw = b"".join(
b"\x00" + b"".join( # mỗi hàng PNG mở đầu bằng 1 byte bộ lọc
b"\xff\x00\x00" if a <= x < b and a <= y < b else b"\xff" * 3
for x in range(size))
for y in range(size))
def chunk(kind, data):
crc = struct.pack(">I", zlib.crc32(kind + data))
return struct.pack(">I", len(data)) + kind + data + crc
ihdr = struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0)
return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
png = demo_png(48)
b64 = base64.b64encode(png).decode("ascii") # 3 byte -> 4 ký tự
data_url = f"data:image/png;base64,{b64}"
print(f"PNG bytes: {len(png)}")
print(f"base64 chars: {len(b64)}")
print(f"ratio: {len(b64) / len(png):.3f}")
print(f"data URL start: {data_url[:40]}...")
# Giải mã ngược phải ra đúng byte ban đầu
print("round trip ok: ", base64.b64decode(b64) == png)

Lần chạy đầu cần tải Python, khoảng 10 MB.

Ảnh thử nặng 118 byte, chuỗi base64 dài 160 ký tự, đúng công thức 4⌈118/3⌉=4⋅40=1604\lceil 118/3\rceil = 4 \cdot 40 = 160. Tỉ lệ 1.356 hơi lớn hơn 4/34/3 vì 118 không chia hết cho 3: 118=3⋅39+1118 = 3 \cdot 39 + 1, nên nhóm cuối chỉ có 1 byte và được đệm thêm hai dấu = cho đủ bốn ký tự. Data URL mở đầu bằng data:image/png;base64, rồi tới iVBORw0KGgo: chuỗi này mã hóa 8 byte chữ ký mà tệp PNG nào cũng mở đầu bằng. Dòng cuối kiểm tra giải mã ngược ra đúng byte ban đầu.

Nếu chạy ô này bằng Python trên máy, số byte có thể lệch vài byte, vì bản thư viện nén zlib mỗi nơi một khác; công thức thì vẫn đúng.

Ô này dùng lại ảnh thử ở trên (tạo tệp anh-thu.png nếu chưa có), ghép data URL với kiểu MIME đoán từ đuôi tệp, rồi hỏi mô hình. Đổi IMAGE_PATH thành đường dẫn tới ảnh của bạn để hỏi về ảnh thật; mimetypes.guess_type sẽ đổi tiền tố thành image/jpeg khi tệp là .jpg. Với đuôi tệp lạ, hàm này trả None, và tiền tố thành data:None;base64,: khi đó hãy ghi kiểu MIME bằng tay.

Chạy trên máy bạn

Hỏi mô hình về một tấm ảnh

Cần cài
pip install openai
Biến môi trường
OPENAI_BASE_URL (tùy chọn), OPENAI_API_KEY, LLM_MODEL
# Hỏi một mô hình vision về một ảnh, gửi ảnh dạng data URL base64.
import base64
import mimetypes
import os
import struct
import zlib
from openai import OpenAI
def demo_png(size):
# Ảnh thử: nền trắng, hình vuông đỏ ở giữa (giống ô chạy trên trang)
a, b = size // 4, 3 * size // 4
raw = b"".join(
b"\x00" + b"".join(
b"\xff\x00\x00" if a <= x < b and a <= y < b else b"\xff" * 3
for x in range(size))
for y in range(size))
def chunk(kind, data):
crc = struct.pack(">I", zlib.crc32(kind + data))
return struct.pack(">I", len(data)) + kind + data + crc
ihdr = struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0)
return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
IMAGE_PATH = "anh-thu.png" # đổi thành đường dẫn tới ảnh của bạn
if not os.path.exists(IMAGE_PATH):
with open(IMAGE_PATH, "wb") as f:
f.write(demo_png(48))
with open(IMAGE_PATH, "rb") as f:
b64 = base64.b64encode(f.read()).decode("ascii")
mime = mimetypes.guess_type(IMAGE_PATH)[0] # "image/png", "image/jpeg"...
data_url = f"data:{mime};base64,{b64}"
client = OpenAI(
base_url=os.environ.get("OPENAI_BASE_URL"), # bỏ trống: dùng OpenAI
api_key=os.environ["OPENAI_API_KEY"],
)
MODEL = os.environ["LLM_MODEL"] # phải là mô hình nhìn được ảnh
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": "Bạn trả lời ngắn gọn bằng tiếng Việt."},
{"role": "user", "content": [ # content là danh sách các phần
{"type": "text",
"text": "Ảnh có những hình gì, màu gì, nằm ở đâu?"},
{"type": "image_url", "image_url": {"url": data_url}},
]},
],
)
print(response.choices[0].message.content)

Phần gọi API giống hệt bài trước, trừ content của lượt user là danh sách hai phần. Bạn nhận về một đoạn chữ mô tả ảnh; mỗi lần chạy, câu chữ một khác. Nếu nhận lỗi 400 nói mô hình không hỗ trợ ảnh, hãy đổi LLM_MODEL sang một mô hình vision của nhà cung cấp bạn dùng.

Ô này xin một bức tranh màu nước từ prompt, rồi lưu ra tệp anh-sinh-ra.png.

Chạy trên máy bạn

Sinh ảnh từ prompt

Cần cài
pip install openai
Biến môi trường
OPENAI_BASE_URL (tùy chọn), OPENAI_API_KEY, IMAGE_MODEL
# Sinh một ảnh từ prompt bằng images.generate, rồi lưu ra tệp.
import base64
import os
import openai
from openai import OpenAI
client = OpenAI(
base_url=os.environ.get("OPENAI_BASE_URL"), # bỏ trống: dùng OpenAI
api_key=os.environ["OPENAI_API_KEY"],
)
IMAGE_MODEL = os.environ["IMAGE_MODEL"] # mô hình sinh ảnh, khác LLM_MODEL
prompt = (
"Tranh màu nước: một chiếc xe đạp dựng cạnh quán cà phê vỉa hè "
"ở Hà Nội, buổi sáng, nắng dịu."
)
try:
response = client.images.generate(
model=IMAGE_MODEL,
prompt=prompt,
size="1024x1024",
n=1,
)
except openai.BadRequestError as err:
# Bị kiểm duyệt chặn, hoặc tham số không hợp với mô hình.
# Gửi lại y nguyên thì kết quả vẫn vậy: hãy sửa prompt.
print("Yêu cầu bị từ chối:", err.code or err.message)
raise SystemExit(1)
image = response.data[0]
if image.b64_json: # ảnh trả về dạng chuỗi base64
with open("anh-sinh-ra.png", "wb") as f:
f.write(base64.b64decode(image.b64_json))
print("Đã lưu anh-sinh-ra.png")
else: # vài nơi trả URL tạm: tải về ngay, URL sẽ hết hạn
print("URL của ảnh:", image.url)

Khi thành công, ô in tên tệp đã lưu. Nếu nhà cung cấp trả url thay cho b64_json, ô in link để bạn tải về ngay. Nếu yêu cầu bị từ chối, ô in mã lỗi (ví dụ moderation_blocked ở OpenAI) hoặc thông báo lỗi, rồi dừng. Trường hợp này hãy sửa prompt, đừng chạy lại y nguyên. Theo tài liệu, ô dùng được với OpenAI, và với Gemini qua lớp tương thích (ví dụ trong tài liệu của Gemini truyền thêm response_format="b64_json"). Với Groq hay Ollama thì chưa, vì trang tương thích OpenAI của hai nơi này không có endpoint sinh ảnh (10/2026).

Câu 1/4 Bạn chạy Ollama trên máy và đặt "url": "https://example.com/meo.jpg" trong phần image_url. Yêu cầu bị lỗi. Sửa thế nào để cùng đoạn code chạy được ở nhiều nhà cung cấp nhất?
Câu 2/4 Qua lớp tương thích OpenAI của Gemini, bạn gọi images.generate lần lượt với quality="high" và quality="low". Không có lỗi nào, nhưng hai ảnh chi tiết như nhau. Giải thích hợp lý nhất là gì?
Câu 3/4 Bạn viết dịch vụ tạo video: gửi yêu cầu, nhận id, hỏi trạng thái tới khi completed, rồi lưu link tải vào cơ sở dữ liệu để người dùng bấm xem vào tuần sau. Thiết kế này hỏng ở đâu?
Câu 4/4 Một bức ảnh lan truyền trên mạng xã hội. Bạn kiểm tra và không thấy metadata C2PA nào. Kết luận nào đúng?

Ý chính:

  • Hỏi về ảnh vẫn là chat: content của lượt user là danh sách các phần text và image_url. url nhận link web hoặc data URL data:<MIME>;base64,...; data URL chạy được ở nhiều nhà cung cấp nhất. Base64 dài khoảng 4/34/3 tệp gốc, và ảnh còn tốn token.
  • Sinh ảnh đi qua cửa riêng images.generate, mô hình đọc từ IMAGE_MODEL. Kết quả ở response.data[0]: đọc b64_json, giải mã rồi lưu tệp; xem cả url phòng khi nhà cung cấp trả link tạm. Không phải nơi nào cũng sinh ảnh, và tham số khác nhau giữa các nơi.
  • Video là tác vụ bất đồng bộ: tạo job, giữ id, polling có backoff và thời hạn (hoặc webhook), tải kết quả về ngay. API video đổi rất nhanh (10/2026), nên bài chỉ dạy khuôn này.
  • Lỗi kiểm duyệt không tự hết khi gửi lại. Dùng ảnh AI thì đọc điều khoản dịch vụ và ghi rõ là ảnh AI. C2PA và watermark cho biết nguồn gốc, nhưng thiếu chúng không chứng minh ảnh thật, có chúng không chứng minh nội dung đúng.

Đọc thêm:

  • OpenAI (2026). Images and vision và Image generation. Cách đưa ảnh vào yêu cầu, trường detail, giới hạn định dạng; tham số của images.generate và các lỗi kiểm duyệt. Ví dụ trên trang dùng Responses API, khác Chat Completions của bài này ở cú pháp.
  • Google (2026). OpenAI compatibility, các mục về ảnh. Ảnh đi vào, images.generate qua lớp tương thích, danh sách tham số được nhận và cách dùng extra_body.
  • Google (2026). Image generation. Sinh và sửa ảnh bằng Gemini qua API riêng của Google; trang ghi rằng mọi ảnh sinh ra đều mang watermark SynthID.
  • Groq (2026). Images and Vision. Gửi ảnh cho mô hình vision của Groq bằng link web hoặc data URL base64.
  • Ollama (2026). OpenAI compatibility. Những gì endpoint kiểu OpenAI của Ollama hỗ trợ, gồm ảnh đi vào dạng base64.
  • C2PA (2026). Coalition for Content Provenance and Authenticity. Trang của chuẩn Content Credentials: chuẩn này dùng để làm gì.
  • OpenAI (2026). Video generation with Sora. API đã ngừng ngày 24/9/2026, nhưng trang vẫn là một ví dụ rõ về job bất đồng bộ: các trạng thái, polling, webhook, thời hạn của link tải.

Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu