Lewati ke konten utama

Percakapan dan memori

Rute ini memungkinkan klien membuat, mencantumkan, melanjutkan, dan menghapus riwayat chat; mencantumkan dan menghapus memori jangka panjang; serta menjalankan penghapusan memori "lupakan saya". Rute ini adalah rute aplikasi yang dipasang di root; gunakan /openapi.json pada environment target untuk discovery yang dapat dibaca mesin. Swagger UI tersedia di /docs di luar production.

Tidak ada API WebSocket untuk alur ini. Pelanjutan jawaban menggunakan rute query JSON atau SSE yang didokumentasikan di Query dan search; pengelolaan percakapan dan memori menggunakan rute HTTP di bawah.

Autentikasi dan identitas​

Semua rute memerlukan header API key yang dikonfigurasi. Nama header default adalah X-API-Key.

Rute yang membawa principal juga mengikat kepemilikan ke header principal tepercaya yang diberikan oleh edge atau lapisan backend-for-frontend:

HeaderMakna
X-User-SubjectSubject stabil yang memiliki percakapan dan memori.
X-User-RoleKlaim role; default ke user jika dihilangkan setelah subject diberikan.
X-User-UnitKlaim unit organisasi opsional yang digunakan pemfilteran retrieval berbasis akses ketika identitas terverifikasi.
X-User-LevelKlaim level posisi bilangan bulat opsional yang digunakan pemfilteran retrieval berbasis akses ketika identitas terverifikasi.

Parameter request dan query user_id hanya merupakan fallback lokal untuk development/back-compat. Klien terintegrasi tidak boleh mengandalkan mode fallback ketika autentikasi nonaktif; klien harus menggunakan header principal tepercaya yang diinjeksi edge, BFF, atau gateway dengan penegakan principal diaktifkan. Kode browser tidak boleh menyematkan shared API key dan tidak boleh membiarkan pengguna akhir menetapkan atau memalsukan header X-User-*.

Ketika penegakan principal diaktifkan, identitas yang tidak ada menghasilkan 401 dengan {"detail":"Authenticated principal required"} dan identitas yang tidak valid menghasilkan 401 dengan {"detail":"Invalid authenticated principal"}. Ketika penegakan dinonaktifkan, klaim identitas yang tidak ada atau tidak valid dibuang: fallback user_id dari rute menjadi pemilik jika diberikan, jika tidak UUID sentinel semua nol menjadi pemilik.

Riwayat percakapan​

POST /conversations​

Membuat percakapan eksplisit yang awalnya kosong dan dimiliki subject yang telah ditetapkan. Gunakan ini ketika UI memiliki aksi "chat baru" sebelum pertanyaan pertama. Jika tindakan pengguna pertama adalah query, rute query dapat membuat percakapan secara otomatis.

Body request​

{
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "Aceh budget follow-up"
}
FieldTipeWajibMakna
user_idstring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Klien terintegrasi sebaiknya menghilangkannya dan mengandalkan header principal tepercaya. Harus UUID valid jika diberikan.
titlestring | nullTidakJudul tampilan opsional. Harus tidak kosong jika diberikan.

Tidak ada field request metadata yang diterima. Field tambahan ditolak dengan error validasi FastAPI 422.

Response 201​

{
"id": "00000000-0000-0000-0000-000000000042",
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "Aceh budget follow-up",
"metadata": {
"mode": "explicit-create"
}
}
FieldTipeMakna
idstring (UUID)Identifier percakapan untuk diteruskan sebagai conversation_id pada /query atau /query/stream.
user_idstring (UUID)Subject pemilik yang telah ditetapkan. Ini adalah subject principal tepercaya jika ada.
titlestring | nullJudul yang disimpan.
metadataobjectUntuk pembuatan eksplisit, saat ini {"mode":"explicit-create"}.

GET /conversations​

Mencantumkan percakapan milik subject yang telah ditetapkan. Tidak ada pagination.

Parameter query​

ParameterTipeWajibMakna
user_idstring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Diabaikan ketika header principal tepercaya menetapkan subject.

Response 200​

[
{
"id": "00000000-0000-0000-0000-000000000044",
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "Apa kewenangan Baitul Mal?",
"metadata": {
"mode": "m1-agentic"
}
},
{
"id": "00000000-0000-0000-0000-000000000042",
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "Aceh budget follow-up",
"metadata": {
"mode": "explicit-create"
}
}
]

Urutan adalah berdasarkan waktu pembuatan terbaru, dengan id sebagai tie-breaker deterministik. Riwayat kosong mengembalikan [].

GET /conversations/{conversation_id}​

Mengembalikan satu percakapan milik subject beserta semua pesan yang disimpan. Tidak ada pagination dan tidak ada pemangkasan di sisi server pada rute pembacaan kembali ini.

Parameter path dan query​

ParameterLokasiTipeWajibMakna
conversation_idPathstring (UUID)YaIdentifier percakapan.
user_idQuerystring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Diabaikan ketika header principal tepercaya menetapkan subject.

Response 200​

{
"id": "00000000-0000-0000-0000-000000000042",
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "Aceh budget follow-up",
"metadata": {
"mode": "explicit-create"
},
"messages": [
{
"id": "00000000-0000-0000-0000-000000000101",
"trace_id": null,
"role": "user",
"content": "Apa kewenangan Baitul Mal?",
"source_citations": [],
"metadata": {}
},
{
"id": "00000000-0000-0000-0000-000000000102",
"trace_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"role": "assistant",
"content": "Baitul Mal memiliki kewenangan ...",
"source_citations": [
{
"document_id": "00000000-0000-0000-0000-000000000007",
"document_title": "Qanun Aceh Nomor 10 Tahun 2018",
"chunk_id": "00000000-0000-0000-0000-000000000091",
"chunk_ordinal": 12,
"page_number": 4,
"section_path": ["Bab II", "Pasal 5"],
"legal_reference": "Pasal 5",
"char_start": 1200,
"char_end": 1538
}
],
"metadata": {
"conversation": {
"resumed": true,
"history_turns_threaded": 2
},
"retrieval": {
"semantic_candidate_limit": 50,
"lexical_candidate_limit": 50,
"rrf_k": 60,
"rerank_candidate_limit": 60,
"final_top_k": 5,
"reranker_enabled": true
},
"synthesis": {
"mode": "m1-agentic",
"note": "M1 agentic Synthesizer stage; retrieval ran via a tool dispatched by the Executor (ADR-0023 / ADR-0024).",
"tool": "vector_search",
"tool_executed": "vector_search",
"directed_synthesis": false,
"no_answer": false,
"faithfulness": {
"mechanism": "deterministic M0 citation-support heuristic",
"action_on_failure": "drop unsupported claim; use cited fallback when no claim survives",
"dropped_claims": [],
"fallback_used": false
}
},
"policy_guard": {
"mechanism": "deterministic-rules-v2",
"action": "allowed",
"categories": []
}
}
}
]
}

Pesan diurutkan dari yang paling lama. Giliran query yang tersimpan berisi pesan pengguna diikuti pesan asisten. Dalam traffic query normal, role adalah user atau assistant; skema wire juga dapat membawa role system atau tool yang tersimpan jika ada.

conversation_id lintas subject dan conversation_id yang tidak dikenal sama-sama mengembalikan 404 dengan bentuk yang sama, misalnya {"detail":"Conversation 00000000-0000-0000-0000-000000000042 not found"}.

DELETE /conversations/{conversation_id}​

Menghapus satu percakapan milik subject yang telah ditetapkan. Semua pesan di dalam percakapan tersebut dihapus dalam operasi database yang sama melalui cascade foreign key pesan.

Path dan parameter query opsional user_id memiliki semantik kepemilikan yang sama dengan GET /conversations/{conversation_id}. Keberhasilan mengembalikan 204 No Content dengan body kosong. Setelah berhasil, percakapan tidak lagi muncul di GET /conversations, dan GET /conversations/{conversation_id} mengembalikan 404.

ID percakapan yang tidak dikenal dan lintas subject sama-sama mengembalikan respons 404 yang sama. Percobaan lintas subject tidak menghapus percakapan atau pesannya.

Skema​

ConversationSummary​

FieldTipeMakna
idstring (UUID)ID percakapan.
user_idstring (UUID)Subject pemilik.
titlestring | nullJudul tampilan. Percakapan yang dibuat query menggunakan 80 karakter pertama query sebagai judul.
metadataobjectMetadata pembuatan/sumber. Pembuatan eksplisit saat ini menggunakan mode: "explicit-create"; percakapan yang dibuat query saat ini menggunakan mode: "m1-agentic".

ConversationDetail​

ConversationDetail memiliki semua field ConversationSummary dan tambahan:

FieldTipeMakna
messagesConversationMessage[]Semua pesan yang tersimpan untuk percakapan, dari yang paling lama.

ConversationMessage​

FieldTipeMakna
idstring (UUID)ID pesan. message_id pada respons /query adalah ID pesan asisten.
trace_idstring | nullID trace Langfuse untuk jawaban asisten. Pesan pengguna dan jawaban tanpa tracing menggunakan null.
rolestringRole pesan yang tersimpan, biasanya user atau assistant.
contentstringTeks pengguna asli atau teks jawaban asisten yang telah melewati guard.
source_citationsCitation[]Kosong untuk pesan pengguna. Untuk pesan asisten, menggunakan kembali objek sitasi yang sama seperti yang dikembalikan /query dan dikeluarkan sebagai event SSE citation.
metadataobject{} untuk pesan pengguna. Pesan asisten menyertakan metadata percakapan, retrieval, sintesis, dan policy guard seperti di atas.

PUT /messages/{message_id}/feedback​

Mencatat like/dislike pemanggil pada jawaban asisten atas pertanyaan yang ia ajukan, dengan alasan dan catatan opsional. Hanya orang yang bertanya yang boleh menilai jawaban: author_id pada pesan pengguna yang dijawab, atau pemilik percakapan bila pesan itu tidak mencatat penulis (baris lama, chat di luar workspace). Satu jawaban hanya punya satu slot umpan balik, sehingga di chat workspace bersama anggota lain, apa pun perannya, akan menimpa keluhan si penanya; bagi mereka pesan itu 404, seolah tidak ada. Umpan balik disimpan pada pesan dan ditulis ke jejak audit lebih dulu; setelah itu rating dikirim sebagai score pada trace Langfuse jawaban tersebut, yang diambil server dari penyimpanan (pemanggil tidak dapat memilih trace lain).

FieldTipeCatatan
rating"like" | "dislike"Wajib. Rating saja sudah merupakan permintaan lengkap.
reasonstring | nullOpsional: wrong_answer, wrong_citation, incomplete, outdated, offensive_or_sara atau other. Nilai lain menghasilkan 422.
notestring | nullTeks bebas opsional, paling banyak 1.000 karakter (lebih dari itu 422). Hanya dibaca peninjau; tidak pernah dikirim ke Langfuse atau disalin ke jejak audit.
{"rating":"dislike","reason":"wrong_citation","note":"Pasal yang dikutip bukan Pasal 7."}

Respons berhasil:

{"message_id":"00000000-0000-0000-0000-000000000102","rating":"dislike","linked":true}

linked bernilai false ketika umpan balik tersimpan tetapi tidak sampai ke Langfuse: jawaban tidak memiliki trace, atau Langfuse sedang tidak tersedia. Respons tetap 200; jangan di-retry dan jangan tampilkan error. Label alasan yang disarankan: Jawaban salah, Sitasi salah, Tidak lengkap, Kedaluwarsa, SARA/menyinggung, Lainnya.

Operasi ini idempotent: PUT yang diulang menyimpan nilai yang sama dan memakai ID score Langfuse deterministik yang sama, dan PUT berikutnya menggantikan rating, alasan, dan catatan sebelumnya. dislike membuka jawaban untuk ditinjau admin, dan membukanya kembali setelah admin menyelesaikannya; tinjauan yang sedang berjalan tidak direset. like berikutnya tidak menutupnya. Pesan yang tidak dikenal, bukan pesan asisten, atau jawaban atas pertanyaan yang tidak diajukan pemanggil mengembalikan 404. Hanya rating serta ID message/trace yang opaque yang dikirim ke Langfuse—bukan teks jawaban, catatan, sumber, atau kredensial.

:::note Perubahan kontrak (3 Nov 2026)

reason dan note adalah field baru yang opsional, sehingga panggilan yang hanya berisi rating tidak berubah. linked kini boolean, bukan selalu true, dan respons 409 (jawaban tanpa trace) serta 503 (Langfuse tidak tersedia) dihapus: keduanya kini mengembalikan 200 dengan linked: false, dan umpan balik tetap tersimpan. Di chat workspace, hanya anggota yang mengajukan pertanyaan yang boleh menilai jawabannya; anggota lain kini mendapat 404.

:::

Citation​

Sitasi dokumen mencakup field berikut:

FieldTipeMakna
document_idstring (UUID)ID dokumen sumber.
document_titlestringJudul dokumen sumber.
chunk_idstring (UUID)ID chunk sumber.
chunk_ordinalintegerOrdinal chunk di dalam dokumen sumber.
page_numberinteger | nullNomor halaman jika diketahui.
section_pathstring[]Label bagian hierarkis. Array kosong jika tidak tersedia.
legal_referencestring | nullReferensi hukum jika diketahui.
char_startinteger | nullOffset karakter awal jika diketahui.
char_endinteger | nullOffset karakter akhir jika diketahui.

Sitasi catatan percakapan memakai kind: "history", turn_index, serta document_id / chunk_id bernilai null. turn_index adalah indeks pertukaran pengguna asli, dimulai dari nol, bukan posisi dalam jendela riwayat terbatas. Kutipan asisten berada pada pertukaran yang dimulai oleh giliran pengguna itu; sitasi tersebut membuktikan ucapan terdahulu, bukan fakta dokumen dalam kutipan.

Field berikut bersifat kondisional dan hanya muncul ketika sumber membawa scope akses:

FieldTipeMakna
confidentialtrueHanya ada untuk materi sumber rahasia.
owning_org_unitstringHanya ada ketika sumber dibatasi pada unit organisasi.
min_position_levelintegerHanya ada ketika sumber memiliki level posisi minimum.

Alur resume​

Ada dua cara untuk memperoleh ID percakapan:

  1. Percakapan eksplisit: panggil POST /conversations, lalu teruskan id yang dikembalikan sebagai conversation_id di /query atau /query/stream.
  2. Percakapan yang dibuat query: hilangkan conversation_id pada panggilan /query atau /query/stream pertama. Jika persistensi selesai, rute query membuat percakapan baru, memberi judul dari 80 karakter pertama query, menyimpan pesan pengguna dan asisten, lalu mengembalikan conversation_id baru.

Pengecualian persistensi timeout dan penanganan ID mengikuti ADR-0041.

Untuk melanjutkan, kirim conversation_id yang sama. Server memeriksa kepemilikan sebelum pekerjaan model. Batas riwayat terbaru menghitung pesan, bukan pasangan pengguna/asisten; setiap pesan dan seluruh jendela terbaru memiliki batas karakter yang dikonfigurasi. Pertanyaan pengingatan atau campuran pada chat biasa milik pemanggil yang tidak dibagikan juga menerima pertukaran pertama dengan batas tersendiri. Seluruh prompt generasi tetap mengikuti jendela konteks model, dengan pemangkasan riwayat lain yang eksplisit sebelum pertukaran pertama bila perlu. GET /conversations/{id} tetap menampilkan transkrip tersimpan.

Judge intent self-hosted yang ketat membedakan pengingatan percakapan murni dari pertanyaan korpus, dengan override recall untuk status unggahan. Pengingatan murni tidak menyediakan tool dokumen, portal, atau MCP. Judge self-hosted independen hanya menerima klaim dan catatan pengguna yang dibatasi, lalu harus mengembalikan dukungan dengan ID pesan yang dikenal. Judge tidak melihat jawaban asisten atau konteks generator; hasil tidak valid atau tidak tersedia ditolak. Ucapan asisten terdahulu hanya dapat dikutip sebagai ucapan, bukan bukti fakta korpus.

Peningkatan memori hanya berlaku untuk jawaban chat biasa milik pemanggil yang tidak dibagikan. Ekspor dan chat bersama tetap memakai riwayat terbaru yang dibatasi seperti sebelumnya; keduanya tidak menerima pertukaran pertama atau paket bukti tambahan. Ekspor memakai pemeriksaan artefak yang sudah ada, bukan dukungan recall baru. Pembicara yang tidak diketahui tidak dapat mendukung recall pribadi.

Pemeriksaan dukungan independen dan pengecualian riwayat USER REG-256/REG-264 pada jalur korpus lama dijelaskan dalam referensi faithfulness gate.

Pemakaian ulang kutipan dokumen, batas prefiks tersimpan, dan pencarian tambahan dijelaskan pada Pertanyaan lanjutan dokumen.

source_citations pada pesan asisten adalah salinan persisten dari sitasi jawaban. UI dapat merender sitasi dari respons /query langsung, lalu membangun kembali tampilan sitasi yang sama dari GET /conversations/{id} tanpa melakukan query ulang.

Memori jangka panjang​

Memori jangka panjang adalah catatan singkat per subject yang disimpan sistem tentang seorang pengguna lintas percakapan: preferensinya (bahasa, panjang, format jawaban) dan konteks kerjanya (regulasi atau topik yang sering ia tanyakan). Sebuah percakapan diringkas menjadi paling banyak tiga memori setelah 30 menit tanpa pesan baru; setiap pertanyaan berikutnya membawa memori paling relevan milik pengguna itu. Memori tidak pernah berisi fakta dokumen, dan jawaban tidak pernah mengutip memori sebagai sumber. Rancangan tulis/baca lengkap ada di Memori Jangka Panjang.

Rute ini berfungsi baik memori diaktifkan maupun tidak, sehingga pengguna selalu dapat melihat dan menghapus apa yang disimpan tentang dirinya. Rute ini hanya menampilkan memori aktif milik subject yang telah ditetapkan. Pada deployment yang mengisi ACEH_RAG_MEMORY__SUBJECT_ALLOWLIST, subject di luar daftar itu tidak memiliki memori di sana: GET mengembalikan [], DELETE /memories mengembalikan {"removed_count":0}, dan DELETE /memories/{memory_id} mengembalikan 404.

GET /memories​

Menampilkan memori aktif milik subject, dari yang terbaru. Tidak ada paginasi; satu subject menyimpan paling banyak ACEH_RAG_MEMORY__MAX_ACTIVE_PER_USER (default 30).

Parameter query​

ParameterTipeWajibMakna
user_idstring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Diabaikan ketika header principal tepercaya menetapkan subject.

Response 200​

[
{
"id": "00000000-0000-0000-0000-000000000501",
"type": "preference",
"text": "Pengguna lebih suka jawaban singkat dalam Bahasa Indonesia.",
"importance": 0.7,
"status": "active",
"source_conversation_id": "00000000-0000-0000-0000-000000000301",
"created_at": "2026-10-16T09:20:31.123456+00:00"
}
]

Jika belum ada memori, responsnya [].

DELETE /memories/{memory_id}​

Menghapus satu memori milik subject secara permanen.

Parameter path dan query​

ParameterLokasiTipeWajibMakna
memory_idPathstring (UUID)YaID memori.
user_idQuerystring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Diabaikan ketika header principal tepercaya menetapkan subject.

Response 204​

Penghapusan yang berhasil mengembalikan 204 No Content dengan body kosong. Jangan memanggil response.json() untuk respons ini; gunakan kode status sebagai hasilnya.

memory_id yang tidak dikenal dan memory_id milik subject lain sama-sama mengembalikan 404, misalnya {"detail":"Memory 00000000-0000-0000-0000-000000000501 not found"}.

DELETE /memories​

Menjalankan penghapusan "lupakan saya" untuk subject: setiap baris memori, termasuk yang sudah digantikan oleh pembaruan, dihapus permanen.

Parameter query​

ParameterTipeWajibMakna
user_idstring (UUID)TidakHanya pemilik fallback lokal ketika auth-off. Default: UUID sentinel semua nol. Diabaikan ketika header principal tepercaya menetapkan subject.

Response 200​

{
"removed_count": 3
}

removed_count adalah jumlah baris memori yang dihapus. Jika tidak ada, responsnya {"removed_count":0}. Rute ini hanya menghapus memori jangka panjang; menghapus percakapan tidak menghapus memori yang ditulis darinya, dan rute ini tidak menghapus percakapan.

MemorySummary​

FieldTipeMakna
idstring (UUID)ID memori yang digunakan oleh DELETE /memories/{memory_id}.
type"preference" | "fact"Preferensi (bahasa, panjang, format) atau fakta yang dibagikan pengguna tentang dirinya atau pekerjaannya.
textstringSatu kalimat yang ditulis model dari percakapan pengguna sendiri, diperiksa Government Policy Guard sebelum disimpan. Escape saat dirender.
importancenumber0.0 hingga 1.0; salah satu bobot peringkat recall.
status"active"Hanya memori aktif yang ditampilkan.
source_conversation_idstring | nullPercakapan asal memori.
created_atstringWaktu pembuatan ISO 8601.

Memori dalam jawaban​

Ketika memori aktif, jawaban /query yang menyatakan ulang sebuah memori (misalnya "apa yang kamu ingat tentang saya?") membawa entri claim map {"kind": "memory", "memory_id": "<id>", "chunk_id": null, "document_id": null}. Seperti kutipan history, entri ini tidak pernah masuk ke citations atau source_citations, sehingga daftar dokumen tidak pernah menampilkan memori. provenance_complete=true dengan citations=[] adalah kombinasi yang sah untuk jawaban yang murni dari memori.

Perilaku status dan error​

StatusDi manaBody
200Rute GET yang berhasil dan DELETE /memoriesBody respons JSON.
201POST /conversationsConversationSummary.
204DELETE /conversations/{conversation_id} dan DELETE /memories/{memory_id}Body kosong.
401API key tidak ada/tidak valid, atau principal tidak ada/tidak valid ketika penegakan principal diaktifkan{"detail":"..."}.
404ID percakapan/memori tidak dikenal atau lintas subject{"detail":"Conversation {id} not found"} atau {"detail":"Memory {id} not found"}.
422Kegagalan validasi FastAPI, termasuk tipe field JSON yang salah atau field tambahan pada POST /conversationsArray detail validasi FastAPI.

Aplikasi tidak menambahkan header CORS tingkat aplikasi. Integrasi browser sebaiknya memanggil BFF same-origin atau endpoint yang dikelola gateway.

Contoh curl​

Tetapkan variabel environment sekali:

BASE_URL="https://api.example.gov"
API_KEY="replace-with-real-key" # pragma: allowlist secret
SUBJECT="00000000-0000-0000-0000-000000000001"

Buat percakapan eksplisit:

curl -sS -X POST "$BASE_URL/conversations" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT" \
-H "X-User-Role: user" \
-H "Content-Type: application/json" \
-d '{"title":"Aceh budget follow-up"}'

Cantumkan percakapan:

curl -sS "$BASE_URL/conversations" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Ambil percakapan untuk rendering resume/riwayat:

curl -sS "$BASE_URL/conversations/00000000-0000-0000-0000-000000000042" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Hapus percakapan beserta semua pesannya:

curl -sS -X DELETE "$BASE_URL/conversations/00000000-0000-0000-0000-000000000042" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Lanjutkan dengan query JSON:

curl -sS -X POST "$BASE_URL/query" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT" \
-H "Content-Type: application/json" \
-d '{"query":"Ringkas jawaban sebelumnya dalam 3 poin.","conversation_id":"00000000-0000-0000-0000-000000000042"}'

Cantumkan memori:

curl -sS "$BASE_URL/memories" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Hapus satu memori dan tangani 204:

curl -i -X DELETE "$BASE_URL/memories/00000000-0000-0000-0000-000000000501" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Lupakan semua memori jangka panjang untuk subject tersebut:

curl -sS -X DELETE "$BASE_URL/memories" \
-H "X-API-Key: $API_KEY" \
-H "X-User-Subject: $SUBJECT"

Contoh TypeScript​

Gunakan shared API key hanya pada server/BFF tepercaya. Browser memanggil rute same-origin yang menetapkan subject dari sesi web terautentikasi.

type Citation = {
document_id: string;
document_title: string;
chunk_id: string;
chunk_ordinal: number;
page_number: number | null;
section_path: string[];
legal_reference: string | null;
char_start: number | null;
char_end: number | null;
confidential?: true;
owning_org_unit?: string;
min_position_level?: number;
};

type ConversationSummary = {
id: string;
user_id: string;
title: string | null;
metadata: Record<string, unknown>;
};

type ConversationMessage = {
id: string;
trace_id: string | null;
role: "user" | "assistant" | "system" | "tool" | string;
content: string;
source_citations: Citation[];
metadata: Record<string, unknown>;
};

type ConversationDetail = ConversationSummary & {
messages: ConversationMessage[];
};

type MemorySummary = {
id: string;
type: "preference" | "fact";
text: string;
importance: number;
status: "active";
source_conversation_id: string | null;
created_at: string;
};

async function acehApi<T>(path: string, init: RequestInit, subject: string): Promise<T> {
const response = await fetch(`${process.env.ACEH_API_BASE_URL}${path}`, {
...init,
headers: {
"X-API-Key": process.env.ACEH_API_KEY!,
"X-User-Subject": subject,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
});

if (response.status === 204) {
return undefined as T;
}

const body = await response.json();
if (!response.ok) {
const message = typeof body?.detail === "string" ? body.detail : "API request failed";
throw new Error(message);
}
return body as T;
}

export async function createConversation(subject: string, title?: string) {
return acehApi<ConversationSummary>(
"/conversations",
{ method: "POST", body: JSON.stringify({ title: title ?? null }) },
subject,
);
}

export async function listConversations(subject: string) {
return acehApi<ConversationSummary[]>("/conversations", { method: "GET" }, subject);
}

export async function getConversation(subject: string, id: string) {
return acehApi<ConversationDetail>(`/conversations/${id}`, { method: "GET" }, subject);
}

export async function deleteConversation(subject: string, id: string) {
await acehApi<void>(`/conversations/${id}`, { method: "DELETE" }, subject);
}

export async function listMemories(subject: string) {
return acehApi<MemorySummary[]>("/memories", { method: "GET" }, subject);
}

export async function deleteMemory(subject: string, id: string) {
await acehApi<void>(`/memories/${id}`, { method: "DELETE" }, subject);
}

export async function forgetMe(subject: string) {
return acehApi<{ removed_count: number }>("/memories", { method: "DELETE" }, subject);
}

Komponen browser sebaiknya memanggil endpoint BFF same-origin milik Anda, bukan API upstream secara langsung:

async function browserListHistory(): Promise<ConversationSummary[]> {
const response = await fetch("/bff/aceh/conversations", { credentials: "include" });
if (!response.ok) throw new Error("Could not load conversation history");
return response.json();
}

async function browserDeleteMemory(id: string): Promise<void> {
const response = await fetch(`/bff/aceh/memories/${id}`, {
method: "DELETE",
credentials: "include",
});
if (response.status === 204) return;
const body = await response.json().catch(() => null);
throw new Error(body?.detail ?? "Could not delete memory");
}