Thuta Learning
Local AI / Local LLM
ProjectsAIbeginner

Project: Local Chat Assistant (မြန်မာ System Prompt ပါ)

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Project: Local Chat Assistant (မြန်မာ System Prompt ပါ) concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး architecture ထဲမှာ data/request ဘယ်လိုစီးဆင်းသလဲ ခြေရာခံနိုင်ရန်
  • ကိုယ့် hardware/use case အတွက် ဘယ်လို ရွေးချယ်သင့်သလဲ ဆုံးဖြတ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

ဒီ project ဟာ အရင် chapter တွေက သင်ခဲ့တဲ့ concept လေးခုကို တစ်ခုတည်းသော application ထဲမှာ ပေါင်းစပ်ထားတာပါ။ Chapter တစ်ခုချင်းစီက ဒါတွေကို သီးခြားစီ မိတ်ဆက်ခဲ့တာဖြစ်ပေမယ့်၊ တကယ့် chat assistant တစ်ခုမှာတော့ ဒီလေးမျိုးလုံးဟာ request တစ်ခုတည်းအတွင်းမှာ အတူတကွ အလုပ်လုပ်ဖို့ လိုအပ်ပါတယ်။

  • local server ရဲ့ chat completions endpoint
  • system prompt
  • token-by-token streaming
  • conversation history

Chat completions endpoint — Ollama ဖြစ်ဖြစ်၊ llama.cpp server ဖြစ်ဖြစ်၊ OpenAI-compatible local runtime ဘယ်ဟာဖြစ်ဖြစ် request shape အတူတူပါပဲ — က messages array ကို လက်ခံပြီး completion ကို ပြန်ပေးပါတယ်၊ ဒါပေမယ့် နောက်ကွယ်က model မှာ ရှေ့က message အကြောင်း memory လုံးဝ မရှိပါဘူး။ Call တိုင်းက stateless inference pass အသစ်တစ်ခုချင်းစီပါ။

ဒါကြောင့် ဒီ project ရဲ့ backend route က turn တိုင်းမှာ conversation တစ်ခုလုံးကို ပြန်ပို့ပါတယ် — Burmese-first tutor persona သတ်မှတ်တဲ့ system message၊ ပြီးရင် ရှေ့က user/assistant message အားလုံး အစဉ်လိုက်၊ နောက်ဆုံးမှာ မေးခွန်းအသစ်။ History ကို ချန်ထားရင် model က သူဘယ်သူဖြစ်တယ် ဘာတွေ ဆွေးနွေးခဲ့တယ်ဆိုတာ လုံးဝ မေ့သွားပါလိမ့်မယ်။

Streaming ကလည်း အရေးကြီးပါတယ် — local အဖြေတစ်ခုလုံး token တစ်ခုချင်းစီ generate ဖြစ်ဖို့ စက္ကန့်အနည်းငယ် ကြာနိုင်ပြီး၊ token နောက်ဆုံးရောက်မှ ဘာမှ မပြဘဲထားရင် model က မှန်ကန်စွာ အလုပ်လုပ်နေပေမယ့် site ပျက်နေသလို ခံစားရတတ်ပါတယ်။ Frontend က response ကို stream အနေနဲ့ ဖတ်ပြီး token ရောက်တိုင်း ထည့်ပေးတာပါ။

Context window ဟာ ဒီနေရာမှာ real constraint ဖြစ်လာသည်

နောက်ဆုံးအနေနဲ့ ဒီ project ဟာ context window budget ဆိုတာ abstract number တစ်ခုအဖြစ်ကနေ real constraint တစ်ခုဖြစ်လာတဲ့နေရာပါ — resend လုပ်တဲ့ message တိုင်းက token cost ရှိလို့ conversation ရှည်လာရင် model ရဲ့ window ထဲ ဆက်ဝင်နေအောင် trim (သို့) summarize လုပ်ရပါလိမ့်မယ်၊ context-window chapter က သတိပေးခဲ့သလိုပါပဲ။

text
LOCAL CHAT ASSISTANT DATA FLOW
------------------------------
BROWSER              BACKEND ROUTE               LOCAL MODEL API
(chat UI)             (/api/chat)                  (e.g. Ollama)

 [user submits]  ---POST--->  add system prompt  --->  POST /api/chat
  messages[]                   + full history           stream: true
                                      |                       |
                                      |<--- token, token, ... ----|
 [tokens render] <---stream--   pipes response straight through
      |
      v
 messages[] += { role: assistant, content: full reply }
      |
      v
 NEXT turn re-sends the WHOLE messages[] array again --
 the model itself is stateless between calls; the array IS the memory.

 history tokens + new reply tokens must fit the model's context window.

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Project ကို အပိုင်းနှစ်ပိုင်းခွဲထားပါ — backend route တစ်ခု၊ frontend chat panel တစ်ခု။

Backend route ထူထောင်ခြင်း

System prompt နဲ့ local model ရဲ့ address ကို ပိုင်ဆိုင်ထားတဲ့ route (ဥပမာ POST /api/chat) ဖြစ်ပြီး browser က local server ကို တိုက်ရိုက် ဆက်သွယ်ခွင့် လုံးဝမရှိစေရဘူး — ဒါမှ base URL နဲ့ နောင် API key ကို client-side code ထဲက ဝေးအောင် ထားနိုင်မှာပါ။

History နဲ့ system prompt ပေါင်းစပ်ပြီး forward လုပ်ခြင်း

Route က frontend က ပို့လိုက်တဲ့ conversation array ကို လက်ခံပြီး Burmese-tutor system message ကို ရှေ့ဆက်ထည့်ပြီး local server ရဲ့ chat completions endpoint ဆီ ပို့ပါတယ်။

Response ကို stream: true နဲ့ pipe ဖြတ်ပေးခြင်း

stream: true သတ်မှတ်ပြီး response body ကို buffer မလုပ်ဘဲ browser ဆီ ရောက်ရောက်ချင်း တိုက်ရိုက် pipe ဖြတ်ပေးပါတယ်။

Frontend chat panel တည်ဆောက်ခြင်း

Conversation state ကို {role, content} message array အနေနဲ့ ထိန်းသိမ်းထားပြီး submit တိုင်းမှာ array တစ်ခုလုံးကို ပို့ပါတယ်။

Token တွေကို ရောက်တိုင်း render ခြင်း

Fetch API ရဲ့ reader ကို သုံးပြီး stream ကို ဖတ်ပြီး decode လုပ်ထားတဲ့ chunk တိုင်းကို assistant message နောက်ဆုံးထဲ ဆက်ထည့်ပါတယ်; stream ပြီးသွားရင် assistant message အပြည့်အစုံကို history array ထဲ push ပါတယ်။

ဒီ project အတွက် "Done" ဆိုတာက အောက်ပါအချက်များ ပြည့်စုံရမည်ဖြစ်သည် —

  • မြန်မာဘာသာနဲ့ မေးခွန်းတစ်ခု ပို့ရင် system prompt ရဲ့ ဩဇာနဲ့ သင့်တော်တဲ့ မြန်မာအဖြေ ရခြင်း
  • reply က တစ်ခါတည်း ပေါ်မလာဘဲ stream ဖြစ်ဖြစ် မြင်ရခြင်း
  • ပထမအဖြေပေါ် မှီခိုတဲ့ follow-up မေးခွန်းလည်း မှန်ကန်စွာ အလုပ်လုပ်ခြင်း
  • local server ကို disconnect လုပ်လိုက်ရင်လည်း တိတ်ဆိတ်စွာ hang မဖြစ်ဘဲ error message မြင်ရခြင်း

အတူတူ စမ်းရေးကြည့်မယ်

typescript
// app/api/chat/route.ts -- backend proxy to a local model server.
// The browser never talks to the local server directly: this route owns
// the system prompt and the local base URL.
import { NextRequest } from "next/server";

const LOCAL_MODEL_URL = "http://localhost:11434/api/chat";
const MODEL_NAME = "llama3.1:8b";

const SYSTEM_PROMPT = `You are a patient tutor. Answer in Burmese first,
in clear everyday language, and give the English technical term in
parentheses the first time it appears. Keep answers short unless the
user asks for more detail. If you don't know something, say so plainly
instead of guessing.`;

type ChatMessage = { role: "user" | "assistant"; content: string };

export async function POST(req: NextRequest) {
  const { messages } = (await req.json()) as { messages: ChatMessage[] };

  const upstream = await fetch(LOCAL_MODEL_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: MODEL_NAME,
      // System prompt + full prior history + newest message, every turn --
      // the model itself remembers nothing between requests.
      messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
      stream: true,
    }),
  });

  if (!upstream.ok || !upstream.body) {
    return new Response(
      JSON.stringify({ error: "Local model server is unreachable." }),
      { status: 502, headers: { "Content-Type": "application/json" } },
    );
  }

  // Re-stream the upstream NDJSON response straight through to the browser
  // instead of buffering the whole reply before responding.
  return new Response(upstream.body, {
    headers: { "Content-Type": "application/x-ndjson" },
  });
}

// --- frontend: consume the stream and grow the visible reply ---
//
// Called with the running conversation array and a callback that receives
// the assistant's partial text after every new token.
export async function sendMessage(
  history: ChatMessage[],
  onPartialReply: (text: string) => void,
): Promise<string> {
  const res = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages: history }),
  });

  if (!res.ok || !res.body) {
    const message = "[Error: could not reach the local model. Is it running?]";
    onPartialReply(message);
    return message;
  }

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let assistantText = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop() ?? ""; // keep the last, possibly-incomplete line

    for (const line of lines) {
      if (!line.trim()) continue;
      const chunk = JSON.parse(line) as {
        message?: { content: string };
        done: boolean;
      };
      if (chunk.message?.content) {
        assistantText += chunk.message.content;
        onPartialReply(assistantText); // repaint with the growing reply
      }
    }
  }

  return assistantText;
}

Run လုပ်လို့မရနိုင်ပါ

ဒီ code ဟာ ကိုယ်ပိုင် local AI server (ဥပမာ Ollama) ကို ခေါ်သုံးပါတယ် — ဒီ site ရဲ့ browser playground ကနေ visitor ရဲ့ စက်ပေါ်က local server ကို လှမ်းမခေါ်နိုင်ပါ (network access ကန့်သတ်ထားလို့ပါ)။ ကိုယ်တိုင် server run ထားတဲ့ terminal (သို့) file ထဲမှာ ဒီ code ကို run ပြီး စမ်းကြည့်ပါ။

You should see
ပရောဂျက်က မှန်ကန်စွာ run နေရင် user က မေးခွန်းရိုက်ပြီး submit ချလိုက်တာနဲ့ assistant ရဲ့အဖြေက စာလုံးအနည်းငယ်စီ တဖြည်းဖြည်း screen ပေါ်မှာ ပေါ်လာပါတယ် (token တစ်ခုချင်းစီ streaming ဖြစ်နေလို့ပါ)၊ full paragraph တစ်ခုလုံး ချက်ချင်း မပေါ်ပါဘူး။ System prompt ကြောင့် အဖြေက မြန်မာလို အရင်ဦးဆုံး ပြန်လာတတ်ပြီး English technical term ပါလာရင် ပထမဆုံးအကြိမ် parenthesis ထဲ ဖော်ပြပါတယ်။ Follow-up မေးခွန်း မေးရင် ရှေ့အဖြေကို ရည်ညွှန်းနိုင်ပါတယ် (history ကို ပြန်ပို့ထားလို့ပါ)။ Local model server ကို ပိတ်ထားရင် (သို့) မရောက်ရင် backend route က 502 status နဲ့ error message ကို ရှင်းလင်းစွာ ပြန်ပေးပြီး UI ပေါ်မှာ "local model ကို ဆက်သွယ်မရပါ" ဆိုတဲ့ visible error တစ်ခု မြင်ရပါတယ်၊ တိတ်ဆိတ်စွာ hang မဖြစ်ပါဘူး။

၅ မိနစ် စမ်းကြည့်

Conversation history array ကို token count တစ်ခု (ခန့်မှန်းချက်) ကျော်လွန်ရင် အဟောင်းဆုံး message တွေကို ဖြတ်ချ (သို့) summarize လုပ်တဲ့ logic ကို ထည့်ပါ — model ရဲ့ context window ထဲ ဆက်ဝင်နေအောင်။ ပြီးရင် message အရေအတွက် များများနဲ့ စမ်းကြည့်ပြီး trim/summarize က မှန်ကန်စွာ trigger ဖြစ်မဖြစ် စစ်ပါ။

သတိလေးတစ်ချက်

Frontend ကနေ local model server ကို တိုက်ရိုက် ခေါ်ပြီး backend proxy route ကို ကျော်လွှားခြင်း — base URL နဲ့ system prompt ကို client-side ထဲ ဖော်ထုတ်ပစ်တာနဲ့ history ကို ကိုယ့် policy အတိုင်း ထိန်းချုပ်လို့ မရတော့ပါဘူး

Turn တစ်ခုစီမှာ conversation history အစအနကို ပြန်မပို့ဘဲ message နောက်ဆုံးတစ်ခုတည်း ပို့ခြင်း — model က context လုံးဝ ပျောက်ပြီး ရှေ့စကားပြောခဲ့တာကို လုံးဝ မေ့သွားပါလိမ့်မယ်

Ollama API Reference -- Generate a Chat CompletionLocal AI / Local LLM

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • Frontend ကနေ local model server ကို တိုက်ရိုက် ခေါ်ပြီး backend proxy route ကို ကျော်လွှားခြင်း — base URL နဲ့ system prompt ကို client-side ထဲ ဖော်ထုတ်ပစ်တာနဲ့ history ကို ကိုယ့် policy အတိုင်း ထိန်းချုပ်လို့ မရတော့ပါဘူး
  • Turn တစ်ခုစီမှာ conversation history အစအနကို ပြန်မပို့ဘဲ message နောက်ဆုံးတစ်ခုတည်း ပို့ခြင်း — model က context လုံးဝ ပျောက်ပြီး ရှေ့စကားပြောခဲ့တာကို လုံးဝ မေ့သွားပါလိမ့်မယ်
  • Model (သို့) tool အသစ်တစ်ခုကို production/daily-use workflow ထဲ တိုက်ရိုက်မထည့်ခင် သေးငယ်တဲ့ scale နဲ့ အရင်စမ်းကြည့်ပါ။

လေ့ကျင့်ခန်း

Conversation history array ကို token count တစ်ခု (ခန့်မှန်းချက်) ကျော်လွန်ရင် အဟောင်းဆုံး message တွေကို ဖြတ်ချ (သို့) summarize လုပ်တဲ့ logic ကို ထည့်ပါ — model ရဲ့ context window ထဲ ဆက်ဝင်နေအောင်။ ပြီးရင် message အရေအတွက် များများနဲ့ စမ်းကြည့်ပြီး trim/summarize က မှန်ကန်စွာ trigger ဖြစ်မဖြစ် စစ်ပါ။

You'll know it worked when: ပရောဂျက်က မှန်ကန်စွာ run နေရင် user က မေးခွန်းရိုက်ပြီး submit ချလိုက်တာနဲ့ assistant ရဲ့အဖြေက စာလုံးအနည်းငယ်စီ တဖြည်းဖြည်း screen ပေါ်မှာ ပေါ်လာပါတယ် (token တစ်ခုချင်းစီ streaming ဖြစ်နေလို့ပါ)၊ full paragraph တစ်ခုလုံး ချက်ချင်း မပေါ်ပါဘူး။ System prompt ကြောင့် အဖြေက မြန်မာလို အရင်ဦးဆုံး ပြန်လာတတ်ပြီး English technical term ပါလာရင် ပထမဆုံးအကြိမ် parenthesis ထဲ ဖော်ပြပါတယ်။ Follow-up မေးခွန်း မေးရင် ရှေ့အဖြေကို ရည်ညွှန်းနိုင်ပါတယ် (history ကို ပြန်ပို့ထားလို့ပါ)။ Local model server ကို ပိတ်ထားရင် (သို့) မရောက်ရင် backend route က 502 status နဲ့ error message ကို ရှင်းလင်းစွာ ပြန်ပေးပြီး UI ပေါ်မှာ "local model ကို ဆက်သွယ်မရပါ" ဆိုတဲ့ visible error တစ်ခု မြင်ရပါတယ်၊ တိတ်ဆိတ်စွာ hang မဖြစ်ပါဘူး။

Project: Local Chat Assistant (မြန်မာ System Prompt ပါ) | Thuta Learning