Memperdalam prompt dan chat models: PromptTemplate versus ChatPromptTemplate, pesan system dan human, few-shot prompting, input variables, model profiles per provider, hingga streaming content-block centric v2/v3 dengan version="v3" di stream_events.

Di episode 3, kalian berhasil menjalankan model dan menguasai semua mode invocation. Kualitas output sebuah LLM tidak hanya ditentukan modelnya — justru sangat ditentukan oleh bagaimana kita berbicara dengannya. Episode 4 ini fokus pada dua hal: menyusun prompt dengan benar dan memaksimalkan streaming modern.
Kita akan membedah PromptTemplate dan ChatPromptTemplate, memahami pesan system dan human, menerapkan few-shot prompting, lalu masuk ke fitur v1.x yang lebih mutakhir: model profiles per provider dan content-block streaming v2/v3.
Di episode 2 kita sudah menyentuh ChatPromptTemplate. Sekarang kita bedah lebih dalam, termasuk versi yang lebih sederhana: PromptTemplate.
| Template | Cocok untuk |
|---|---|
PromptTemplate | Satu blok teks tanpa struktur pesan |
ChatPromptTemplate | Urutan pesan system/human/assistant ala API chat |
PromptTemplate cukup untuk tugas sederhana — satu instruksi dengan beberapa variabel:
from langchain_core.prompts import PromptTemplate
template = PromptTemplate.from_template(
"Buat paragraf promosi {produk} untuk audiens {audiens}."
)
pesan = template.invoke({"produk": "kopi robusta", "audiens": "anak muda"})
print(pesan.text)Untuk aplikasi chat, struktur pesan jauh lebih berpengaruh. ChatPromptTemplate menyusun urutan peran: system mendefinisikan perilaku dan konteks global, human adalah input pengguna, dan assistant adalah jawaban model. Model-model modern mengikuti instruksi system dengan sangat baik — di sinilah kepribadian dan batasan aplikasi kalian didefinisikan:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "Kamu adalah asisten keuangan yang selalu menanggapi dengan nada profesional dan menolak memberi saran investasi berisiko."),
("human", "Bolehkah saya menaruh seluruh tabungan ke kripto?"),
])Ketika invoke dipanggil pada template ini, LangChain menyusun daftar pesan berperan yang dikirim ke model. Contoh di atas tidak memakai variabel — untuk membuatnya dinamis, gunakan placeholder di dalam string seperti yang sudah kalian lihat di episode 2.
Terkadang instruksi saja belum cukup — model butuh contoh. Few-shot prompting menyisipkan beberapa contoh input-output agar model memahami pola yang diinginkan. Di ChatPromptTemplate, contoh dimasukkan sebagai pesan berpasangan human/assistant:
contoh = [
("human", "Produk ini 'bagus'."),
("assistant", "Tone: netral. Peringkat: 3/5"),
("human", "Produk ini 'luar biasa'!"),
("assistant", "Tone: antusias. Peringkat: 5/5"),
]
prompt = ChatPromptTemplate.from_messages([
("system", "Klasifikasikan sentimen ulasan produk."),
*contoh,
("human", "Produk ini 'mengecewakan'."),
])
llm = ChatOpenAI(model="gpt-4o-mini")
print(llm.invoke(prompt.invoke({})).content)Perhatikan cara daftar contoh di-unpack dengan operator * — setiap pasangan menjadi pesan tersendiri. Model kini punya "tolok ukur" bentuk jawaban yang diharapkan, sehingga output jauh lebih konsisten daripada sekadar perintah teks.
Placeholder di dalam template adalah input variables — nilai yang wajib disediakan saat invoke. LangChain memvalidasi bahwa semua variabel terisi; jika ada yang kurang, ia melempar error. Kalian juga bisa memberikan nilai bawaan dengan partial, sehingga variabel itu tidak perlu dikirim ulang setiap kali:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "Kamu adalah {peran} yang bekerja di perusahaan {perusahaan}."),
("human", "{pertanyaan}"),
])
prompt_parsial = prompt.partial(perusahaan="TechNusa")
pesan = prompt_parsial.invoke({"peran": "data engineer", "pertanyaan": "Apa itu pipeline?"})
print(pesan.to_messages())Dengan partial, nilai perusahaan terikat pada template, dan hanya peran plus pertanyaan yang perlu disediakan saat invoke. Berguna untuk template yang dipakai ulang dalam konteks yang sama.
Salah satu fitur menarik di v1.x adalah model profiles — profil terpusat yang mendeskripsikan model dari berbagai provider (nama, kapabilitas tool calling, konteks, dan lain-lain). Alih-alih mengingat parameter tiap model, kalian memakai profil yang sudah distandarkan:
from langchain_hub import create_model_profile
from langchain_openai import ChatOpenAI
profil = create_model_profile("openai:gpt-4o")
llm = ChatOpenAI(llm_profile=profil, temperature=0)Kelebihannya: kode aplikasi tetap bersih, dan perpindahan antar provider cukup mengganti string profil — misalnya ke anthropic:claude-3-5-sonnet — tanpa menulis ulang logika. Di episode 16 kita akan memakai profiles untuk strategi fallback antar model.
Di episode 3 kita melihat streaming dasar llm.stream, yang mengalirkan potongan teks. Namun model generasi terbaru tidak hanya mengeluarkan teks — mereka juga mengeluarkan content blocks: teks, panggilan tool, dan reasoning yang terjadi dalam satu respons. Menangani blok-blok ini dengan baik adalah fokus dari streaming v2/v3.
Versi v3 memperkenalkan aliran peristiwa yang terpusat pada blok, lewat stream_events. Contoh nyata — memantau blok teks yang mengalir:
async def pantau_chain():
chain = prompt | ChatOpenAI(model="gpt-4o")
async for event in chain.astream_events({"pertanyaan": "Jelaskan RAG."}, version="v3"):
if event["event"] == "on_chat_model_stream_block":
blok = event["data"]["block"]
if blok["type"] == "text":
print(blok.get("text", ""), end="", flush=True)
asyncio.run(pantau_chain())Yang terjadi di sini: setiap event on_chat_model_stream_block membawa satu blok; kita filter blok bertipe text dan menampilkannya. Untuk blok bertipe tool_call, kalian bisa mengekstrak argumen tool yang sedang disiapkan model — pola yang akan dipakai penuh saat membangun agent di episode 8 dan 13.
Info
Menentukan version="v3" secara eksplisit membuat kontrak event streaming kalian stabil terhadap perubahan ke depannya. Tanpa argumen ini, perilaku default dapat berubah mengikuti versi langchain-core yang terpasang — sesuatu yang menyulitkan debugging di production.
Episode 4 mengasah kemampuan kalian berkomunikasi dengan model: memilih PromptTemplate atau ChatPromptTemplate, menyusun pesan system dan human, menerapkan few-shot prompting, mengikat variabel dengan partial, memakai model profiles per provider, dan menangani streaming content-block v2/v3 dengan version="v3".
Inti yang harus dibawa pulang:
ChatPromptTemplate menyusun urutan pesan berperan; system prompt menentukan perilaku model.partial mengikat nilai bawaan ke template.astream_events(version="v3").Di episode 5 berikutnya kita masuk ke materi yang sangat penting — LCEL: rantai dan komposisi. Kalian akan membangun pipeline sungguhan dengan RunnableSequence, RunnableParallel, RunnablePassthrough, RunnableLambda, dan RunnableMap, plus lifecycle invoke/batch/stream, event streaming, error handling, dan retries. Sampai jumpa!