Output terstruktur
Output terstruktur berarti model menjawab dengan JSON yang bisa langsung di-parse kodemu, bukan teks bebas. Di Chat completions, kamu memintanya lewat response_format. kenari meneruskan field itu ke sebagian model dan membuangnya untuk model lain, dan kenari tidak memeriksa hasilnya. Anggap response_format sebagai permintaan kepada model, dan validasi setiap balasan di kodemu sendiri.
Minta JSON
Section titled “Minta JSON”{"type": "json_object"} meminta satu objek JSON yang valid. Beri tahu juga model di prompt untuk menjawab dengan JSON, karena sebagian provider mewajibkannya.
curl https://kenari.id/v1/chat/completions \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "step-3-7-flash:free", "response_format": {"type": "json_object"}, "messages": [ {"role": "system", "content": "Balas dengan satu objek JSON saja."}, {"role": "user", "content": "Ambil vendor dan total: Faktur dari Toko Maju, total 417000 rupiah."} ] }'Minta skema
Section titled “Minta skema”{"type": "json_schema"} meminta JSON yang sesuai dengan sebuah JSON Schema. Beri skema itu name, dan taruh skemanya di schema.
{ "model": "step-3-7-flash:free", "response_format": { "type": "json_schema", "json_schema": { "name": "invoice", "schema": { "type": "object", "properties": { "vendor": {"type": "string"}, "total": {"type": "integer"} }, "required": ["vendor", "total"] } } }, "messages": [ {"role": "user", "content": "Ambil field faktur ini: Faktur dari Toko Maju, total 417000 rupiah."} ]}Apa yang dilakukan kenari dengan response_format
Section titled “Apa yang dilakukan kenari dengan response_format”kenari tidak menafsirkan response_format. Hasilnya bergantung pada format API yang dipakai provider untuk melayani model. Itu bukan endpoint yang kamu panggil, dan GET /v1/models tidak menampilkannya, jadi rencanakan validasi dan coba lagi di setiap model:
| Model dilayani lewat | Penanganan response_format |
|---|---|
| API gaya OpenAI | Diteruskan apa adanya. Penerapannya bergantung pada model. |
| API gaya Anthropic atau API gaya Responses | Tidak dikirim. Model menjawab hanya dari prompt-mu. |
API apa pun, dan request juga memuat server tool kenari: | Tidak dikirim. |
kenari tidak pernah mengembalikan error hanya karena model mengabaikan field ini, dan GET /v1/models tidak menyebutkan model mana yang menegakkannya. Jika provider menolak field ini, kamu mendapat 400. Jadi sebuah balasan bisa berupa JSON valid, JSON tidak valid, atau JSON yang dibungkus code fence.
Pola yang andal: validasi lalu coba lagi
Section titled “Pola yang andal: validasi lalu coba lagi”Minta JSON, validasi balasan terhadap skema-mu, dan kirim error validasinya kembali jika gagal. Pola ini dapat digunakan di semua model.
import os
from openai import OpenAIfrom pydantic import BaseModel, ValidationError
class Invoice(BaseModel): vendor: str total: int
client = OpenAI( base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"],)
def extract_invoice(text: str, attempts: int = 3) -> Invoice: messages = [ {"role": "system", "content": "Balas dengan satu objek JSON saja."}, {"role": "user", "content": f"Ambil vendor dan total dari: {text}"}, ] for _ in range(attempts): response = client.chat.completions.create( model="step-3-7-flash:free", messages=messages, response_format={ "type": "json_schema", "json_schema": {"name": "invoice", "schema": Invoice.model_json_schema()}, }, ) reply = response.choices[0].message.content or "" try: return Invoice.model_validate_json(reply) except ValidationError as error: messages += [ {"role": "assistant", "content": reply}, {"role": "user", "content": f"Balasan itu tidak valid: {error}. Balas hanya dengan JSON yang sudah diperbaiki."}, ] raise RuntimeError("tidak ada JSON valid setelah beberapa percobaan")
print(extract_invoice("Faktur dari Toko Maju, total 417000 rupiah."))Mengirim response_format sekaligus memvalidasi tidak menambah biaya tersendiri dan membantu di model yang mendukungnya. Setiap percobaan ulang adalah request baru, yang ditagih seperti biasa di model berbayar.
Output terstruktur dengan tool
Section titled “Output terstruktur dengan tool”Memaksa panggilan tool meminta argumen yang berbentuk sesuai skema, dan dapat digunakan di model apa pun dengan tool_call bernilai true. Definisikan satu tool yang input_schema-nya adalah skema-mu, paksa dengan tool_choice, lalu baca argumennya dari panggilan itu. Di Messages:
import os
import anthropic
client = anthropic.Anthropic( base_url="https://kenari.id", api_key=os.environ["KENARI_API_KEY"],)
response = client.messages.create( model="step-3-7-flash:free", max_tokens=1024, tools=[ { "name": "record_invoice", "description": "Catat field sebuah faktur.", "input_schema": { "type": "object", "properties": { "vendor": {"type": "string"}, "total": {"type": "integer"}, }, "required": ["vendor", "total"], }, } ], tool_choice={"type": "tool", "name": "record_invoice"}, messages=[{"role": "user", "content": "Faktur dari Toko Maju, total 417000 rupiah."}],)
invoice = next(block.input for block in response.content if block.type == "tool_use")print(invoice)block.input sudah berupa objek. Kamu tidak perlu menjalankan tool atau mengirim hasilnya kembali. Pola yang sama berlaku di Chat completions dengan function tool dan tool_choice: {"type": "function", "function": {"name": "record_invoice"}}, dengan JSON-nya ada di tool_calls[0].function.arguments. Tetap validasi, dan tangani balasan yang tidak memuat panggilan tool. Lihat Function calling.
Batasan dan jebakan
Section titled “Batasan dan jebakan”/v1/responsestidak mendukung output terstruktur.text.formatdenganjson_objectataujson_schemaditolak dengan400. Pakai Chat completions atau Messages.- Balasan yang terpotong oleh
max_tokensadalah JSON yang tidak valid. Di Chat completions, pastikanfinish_reasonbukanlength. Di Messages, pastikanstop_reasonbukanmax_tokens. Model reasoning memakai sebagianmax_tokensuntuk berpikir, jadi sisakan ruang. Lihat Penalaran. - Jangan mengandalkan aturan skema seperti
minimumataupatternditegakkan. Model berbeda-beda, jadi periksa di kodemu sendiri.