Context Window និងការចងចាំ
មាតិកា
- ម៉ូដែលមិនមានការចងចាំ
- ម៉ូដែលក្លែងក្លាយដែល Stateless
- ការចងចាំគឺគ្រាន់តែជាការផ្ញើ Chat ម្តងទៀត
- Context Window
- ការរាប់ Token៖ ការប៉ាន់ស្មាន
- នៅពេល Window ពេញ
- Trimming៖ រក្សាសារ System និងវេនថ្មីបំផុត
- ការសង្ខេបវេនចាស់
- ថ្លៃដើមកើនឡើងតាមប្រវត្តិ
- Lost in the Middle
- Prompt Caching៖ គំនិត
- Long Context ទល់នឹងការស្វែងរកផ្នែកត្រឹមត្រូវ
- រូបមន្ត៖ Module context
- សាកល្បងដោយខ្លួនឯង
- សំណួរដែលអ្នកនឹងត្រូវគេសួរ
1. ម៉ូដែលមិនមានការចងចាំ
អ្នកប្រាប់ជំនួយការ AI ថា "ខ្ញុំឈ្មោះ Sokha។" ដប់សារក្រោយមក អ្នកសួរថា "តើខ្ញុំឈ្មោះអ្វី?" ហើយវាឆ្លើយត្រូវ។ វាមានអារម្មណ៍ដូចជាជំនួយការចងចាំអ្នក ដូចមិត្តភក្តិម្នាក់ដែរ។
នេះជារឿងគួរឲ្យភ្ញាក់ផ្អើល៖ ខ្លួនម៉ូដែលមិនចងចាំអ្វីទាំងអស់។ នៅក្នុងជំពូក មុនៗ អ្នកបានឃើញអ្វីដែលម៉ូដែលធ្វើ៖ វាអានអត្ថបទ ដាក់ពិន្ទុ token បន្ទាប់ ជ្រើសរើសមួយ ហើយធ្វើម្តងទៀត (ប្រូបាប Temperature និង Sampling)។ នៅពេលវាធ្វើរួច វាចប់។ លេខខាងក្នុងរបស់វា (weight ដែលអ្នកបានជួបក្នុង របៀបហ្វឹកហ្វឺនម៉ូដែល) មិនផ្លាស់ប្តូរទេ។ គ្មានអ្វីអំពីការសន្ទនារបស់អ្នកត្រូវបានរក្សាទុកក្នុងវាទេ។
សូមគិតអំពីមនុស្សឆ្លាតខ្លាំងម្នាក់ដែល គ្មានការចងចាំអ្វីសោះ ហើយភ្ញាក់ឡើងថ្មីៗ ទាក់ទងគ្រប់សំណួរ។ ប្រសិនបើអ្នកប្រគល់សៀវភៅកត់ត្រាដែលមានការសន្ទនាទាំងមូលសរសេរទុក គាត់អាន ហើយឆ្លើយដូចជាគាត់ចងចាំ។ ប្រសិនបើអ្នកប្រគល់ក្រដាសទទេ គាត់មិនដឹងអ្វីអំពីអ្នកទេ។
កម្មវិធី chat ធ្វើដូច្នោះឥតខ្ចោះ។ រាល់ពេលដែលអ្នកចុច Send កម្មវិធីខ្ចប់ ការសន្ទនាទាំងមូលរហូតមកដល់ពេលនេះ ទៅជាអត្ថបទធំមួយ ហើយផ្ញើវាទៅម៉ូដែលម្តងទៀត។ "ការចងចាំ" នៅក្នុងកម្មវិធី មិនមែននៅក្នុងម៉ូដែលទេ។ ការរចនានេះហៅថា stateless៖ រាល់ request ឈរដោយឡែកពីគ្នា។
ជំពូកនេះបង្ហាញល្បិចនោះក្នុងកូដ ពន្យល់ពីដែនកំណត់ដែលវាជួប (context window) ហើយបង្រៀនឧបករណ៍ដែលអ្នកអភិវឌ្ឍ chatbot គ្រប់គ្នាត្រូវការ៖ ការរាប់ token ការកាត់សារចាស់ ការសង្ខេប និងការគិតអំពីថ្លៃដើម។
2. ម៉ូដែលក្លែងក្លាយដែល Stateless
យើងមិនអាចហៅម៉ូដែលពិតបានទេ (គ្មាន key គ្មាន network ក្នុងស៊េរីនេះ) ដូច្នេះយើងសាងសង់
ម៉ូដែលក្លែងក្លាយមួយដែលមានលក្ខណៈសំខាន់មួយ៖ វា stateless ហើយដឹងតែអ្វីដែលនៅក្នុង
array messages ដែលអ្នកប្រគល់ឲ្យប៉ុណ្ណោះ។ វាជាប្រដាប់ក្មេងលេងដែលអាចឆ្លើយសំណួរពីរ
គឺ "តើខ្ញុំឈ្មោះអ្វី?" និង "តើភេសជ្ជៈដែលខ្ញុំចូលចិត្តគឺអ្វី?" ដោយស្វែងរកចម្លើយ
ក្នុងសារមុនៗ។
បង្កើតឯកសារថ្មី src/context.js ហើយបន្ថែមផ្នែកដំបូងនេះ។ អ្នកនឹងបន្ថែមផ្នែកបួនទៀត
នៅពេលជំពូកដំណើរទៅមុខ។
// src/context.js (part 1: the fake model)
// A stateless fake model. It sees ONLY the messages you pass in.
// Statements get "Noted." It answers two questions by searching the
// earlier user messages.
export function fakeModel(messages) {
const last = messages[messages.length - 1].content;
if (!last.includes("?")) return "Noted.";
const question = last.toLowerCase();
const earlier = messages
.slice(0, -1)
.filter((m) => m.role === "user")
.map((m) => m.content);
if (question.includes("my name")) {
for (const text of earlier.reverse()) {
const m = text.match(/my name is (\w+)/i);
if (m) return `Your name is ${m[1]}.`;
}
return "I don't know your name. You have not told me.";
}
if (question.includes("favorite drink")) {
for (const text of earlier.reverse()) {
const m = text.match(/i like (?:to drink )?([\w ]+?)[.!]?$/i);
if (m) return `You like ${m[1]}.`;
}
return "I don't know your favorite drink.";
}
return "I can only answer questions about your name and favorite drink.";
}
សារ (message) នៅទីនេះគឺជា object ដែលមាន role និង content មួយចំនួន។ role មាន
"system" (ការណែនាំពីអ្នកអភិវឌ្ឍ) "user" (អ្វីដែលមនុស្សវាយ) និង "assistant"
(អ្វីដែលម៉ូដែលនិយាយ)។ ជំពូកបន្ទាប់
Prompt, Role និង Structured Output
និយាយអំពីពួកវាទាំងស្រុង។ សម្រាប់ពេលនេះ គ្រាន់តែដឹងថាការសន្ទនាគឺជា array នៃ object ទាំងនេះ
ចាស់បំផុតមកមុន។
សាកល្បងវា។ សួរសំណួរដោយគ្មានអ្វីផ្សេងក្នុង array បន្ទាប់មកសួរម្តងទៀតជាមួយ វេនមុនដែលបានបញ្ចូល៖
// demo.js
import { fakeModel } from "./src/context.js";
console.log(fakeModel([{ role: "user", content: "What is my name?" }]));
// I don't know your name. You have not told me.
const history = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hi, my name is Dara." },
{ role: "assistant", content: "Nice to meet you, Dara." },
{ role: "user", content: "What is my name?" },
];
console.log(fakeModel(history));
// Your name is Dara.
សំណួរដូចគ្នា "ម៉ូដែល" ដូចគ្នា ចម្លើយពីរខុសគ្នា។ ភាពខុសគ្នាតែមួយគត់គឺអ្វីដែលយើងដាក់ក្នុង array។ នោះជាអាថ៌កំបាំងទាំងមូលនៃការចងចាំរបស់ chat។ ទាំងពីរបន្ទាត់មានស្ថេរភាព ពីព្រោះម៉ូដែលក្លែងក្លាយមិនប្រើភាពចៃដន្យទេ។
ម៉ូដែលពិតមានសមត្ថភាពខ្លាំងជាងប្រដាប់ regex នេះឆ្ងាយណាស់ ប៉ុន្តែគោលការណ៍ដូចគ្នា៖ វាអាចប្រើបានតែអ្វីដែលនៅចំពោះមុខវា បូកនឹងចំណេះដឹងទូទៅពីការហ្វឹកហ្វឺន (មិនមែនការពិតអំពីអ្នកទេ)។
3. ការចងចាំគឺគ្រាន់តែជាការផ្ញើ Chat ម្តងទៀត
ឥឡូវសាងសង់រង្វិលជុំដែលកម្មវិធី chat ដំណើរការ។ រក្សា array មួយ។ រាល់វេន បន្ថែមសារ របស់អ្នកប្រើ ផ្ញើ array ទាំងមូល ទៅម៉ូដែល បន្ទាប់មកបន្ថែមចម្លើយចូល array ផងដែរ។
import { fakeModel } from "./src/context.js";
const chat = [{ role: "system", content: "You are a helpful assistant." }];
function say(text) {
chat.push({ role: "user", content: text });
const reply = fakeModel(chat); // the WHOLE history goes in every time
chat.push({ role: "assistant", content: reply });
console.log("You:", text);
console.log("Bot:", reply);
}
say("My name is Sokha.");
say("I like iced tea.");
say("What is my name?");
say("What is my favorite drink?");
console.log(chat.length);
// You: My name is Sokha.
// Bot: Noted.
// You: I like iced tea.
// Bot: Noted.
// You: What is my name?
// Bot: Your name is Sokha.
// You: What is my favorite drink?
// Bot: You like iced tea.
// 9
array ធំឡើងពី 1 សារ (សារ system) ទៅ 9៖ សារអ្នកប្រើបួន ចម្លើយ assistant បួន
បូកសារ system។ រាល់ការហៅ fakeModel ទទួលបាន array វែងជាងលើកមុន។
លទ្ធផលពីរកើតមាន៖ array មានដែនកំណត់ទំហំ (context window) និង អ្នកបង់ថ្លៃសម្រាប់ array ទាំងមូលរាល់វេន។
ប្រសិនបើកម្មវិធីរបស់អ្នកបាត់ array (chat ថ្មី server ចាប់ផ្តើមឡើងវិញ bug) ម៉ូដែល "ភ្លេច" អ្វីៗទាំងអស់។ ការចងចាំល្អតែតាមកូដរបស់អ្នកប៉ុណ្ណោះ។
4. Context Window
Context window គឺជាបរិមាណអត្ថបទអតិបរមា (វាស់ជា token សូមមើល Token និង Tokenizer) ដែលម៉ូដែលអាចមើលក្នុង request មួយ។ វារួមបញ្ចូល អ្វីៗទាំងអស់៖
- សារ system
- សារ user និង assistant មុនៗទាំងអស់
- ឯកសារណាដែលអ្នកបានបិទភ្ជាប់
- សំណួរថ្មី
- និងចម្លើយដែលម៉ូដែលកំពុងសរសេរឥឡូវនេះ។
សូមគិតថាវាជាទំហំនៃតុ។ ម៉ូដែលអាចធ្វើការបានតែជាមួយក្រដាសដែលសមលើតុក្នុងពេលតែមួយ។ តុធំជាងអនុញ្ញាតឲ្យអ្នកលាតក្រដាសបានច្រើនជាង ប៉ុន្តែតុមិនដែលគ្មានដែនកំណត់ទេ។
ទំហំ window ខុសគ្នារវាងម៉ូដែល ហើយផ្លាស់ប្តូរតាមពេលវេលាដូចម៉ូដែលថ្មីៗមកដល់។ ជា ឧទាហរណ៍តែប៉ុណ្ណោះ ម៉ូដែលមួយអាចមាន window 8,000 token ឬ 128,000 ឬច្រើនជាងនេះ។ ពិនិត្យឯកសាររបស់ម៉ូដែលដែលអ្នកប្រើ កុំជឿលេខពីអត្ថបទ blog រួមទាំងអត្ថបទនេះផង។
ចំណុចស្មោះត្រង់ខ្លះអំពី window៖
- window ត្រូវបានចែករំលែករវាង input និង output។ ប្រសិនបើ window គឺ 8,000 token ហើយ prompt របស់អ្នកប្រើ 7,900 ចម្លើយអាចវែងបានតែប្រហែល 100 token ប៉ុណ្ណោះ។
- ធំជាងមិនមែនឥតគិតថ្លៃទេ។ token ច្រើនជាងមានន័យថាវិក្កយបត្រខ្ពស់ជាង និង ជាធម្មតាការឆ្លើយតបយឺតជាង (ផ្នែកទី 9)។
- ការដាក់ចូលបានមិនដូចគ្នានឹងការប្រើបានល្អទេ។ ម៉ូដែលអាចអាន prompt វែង ប៉ុន្តែមិនតែងតែប្រើគ្រប់ផ្នែកស្មើគ្នាទេ (ផ្នែកទី 10)។
- ហេតុអ្វីបានជាមានដែនកំណត់ទាល់តែសោះ? ក្នុងគំនិត attention ពី Embedding និង Attention រាល់ token មើល token ផ្សេងទៀត ដូច្នេះការងារកើនឡើងយ៉ាងលឿនតាមប្រវែង។ វិស្វករបន្តពង្រីក window ប៉ុន្តែតែងតែមានដែនកំណត់។
5. ការរាប់ Token៖ ការប៉ាន់ស្មាន
ដើម្បីស្ថិតក្នុង window អ្នកត្រូវ រាប់ token។ ចំនួនពិតប្រាកដអាស្រ័យលើ tokenizer របស់ម៉ូដែលជាក់លាក់។ កម្មវិធីពិតប្រើ tokenizer ដែលអ្នកផ្តល់ម៉ូដែលផ្តល់ឲ្យ ឬសុំ API រាប់ឲ្យ។
សម្រាប់ការរៀន ក្បួនប្រហាក់ប្រហែល ដ៏សាមញ្ញគឺគ្រប់គ្រាន់ហើយ៖ ក្នុងភាសាអង់គ្លេសធម្មតា token មួយប្រហែល 4 តួអក្សរ។ ដូច្នេះយើងប៉ាន់ស្មាន៖
// src/context.js (part 2)
// Rough estimate: English text averages about 4 characters per token.
// Real tokenizers differ, so treat this as a ballpark, never a bill.
export function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
// A message costs its text plus a few tokens of wrapper (role, markers).
export function messageTokens(message) {
return estimateTokens(message.content) + 4;
}
export function totalTokens(messages) {
return messages.reduce((sum, m) => sum + messageTokens(m), 0);
}
+ 4 គឺជាប្រាក់ឧបត្ថម្ភប្រឌិតសម្រាប់ "ក្រដាសរុំ" ដែល API បន្ថែមជុំវិញសារនីមួយៗ
(role និងសញ្ញាសម្គាល់មួយចំនួន)។ ចំនួនបន្ថែមពិតប្រាកដខុសគ្នាតាមអ្នកផ្តល់សេវា។
ចំណុចសំខាន់គឺសារមានតម្លៃច្រើនជាងអត្ថបទរបស់វាបន្តិច។
សាកល្បងវាលើ corpus រួមពីជំពូកទី 1 និងលើឯកសារវែងជាងដែលបង្កើតដោយធ្វើម្តងទៀត៖
import { corpus } from "./src/corpus.js";
import { estimateTokens } from "./src/context.js";
console.log(estimateTokens("Hello"));
// 2
console.log(corpus.length, estimateTokens(corpus));
// 951 238
const windowSize = 8000; // an example window, not a real model's
console.log(((estimateTokens(corpus) / windowSize) * 100).toFixed(1) + "%");
// 3.0%
const doc = corpus.repeat(40);
console.log(estimateTokens(doc), estimateTokens(doc) > windowSize);
// 9510 true
រឿងកាហ្វេតូចប្រើប្រហែល 3% នៃ window គំរូ 8,000 token។ ច្បាប់ចម្លងសែសិបមិនសមទេ។
ប្រយ័ត្នជាមួយភាសាផ្សេង។ ច្បាប់ 4 តួអក្សរគឺសម្រាប់ភាសាអង់គ្លេស។ tokenizer ច្រើន បំបែកអក្សរខ្មែរ និងអត្ថបទមិនមែនអង់គ្លេសផ្សេងទៀតទៅជា token ច្រើនជាងក្នុងមួយតួអក្សរ ដូច្នេះអត្ថន័យដូចគ្នាអាចប្រើ window ច្រើនជាង ហើយថ្លៃជាង។ កុំទាយ៖ បិទភ្ជាប់អត្ថបទរបស់អ្នក ទៅក្នុងឧបករណ៍រាប់ token លើទំព័របណ្ដាញ tokenizer ពិត (អ្នកផ្តល់សេវាច្រើនផ្សព្វផ្សាយមួយ) ហើយមើល។ នេះជាទម្លាប់ល្អសម្រាប់កម្មវិធីណាដែលបម្រើអ្នកប្រើកម្ពុជា។
6. នៅពេល Window ពេញ
តើមានអ្វីកើតឡើងប្រសិនបើការសន្ទនាធំហួស window? វាអាស្រ័យលើអ្នកផ្តល់សេវា ប៉ុន្តែជម្រើសមាន៖
- API ត្រឡប់កំហុស។ "Request របស់អ្នកវែងពេក។" អ្នកប្រើរបស់អ្នកឃើញការបរាជ័យ។
- កម្មវិធីរបស់អ្នកកាត់ប្រវត្តិ មុនពេលផ្ញើ (ជំពូកនេះ)។
- អ្នកផ្តល់សេវាកាត់ឲ្យអ្នក ដោយទម្លាក់អត្ថបទខ្លះដោយស្ងាត់ៗ។ នេះជាករណីគ្រោះថ្នាក់៖ ម៉ូដែលឆ្លើយដូចជាផ្នែកដែលទម្លាក់មិនដែលមាន ហើយគ្មានអ្នកណាប្រាប់អ្នកទេ។
ដោយសារអ្នកមិនអាចពឹងលើអ្នកផ្តល់សេវាធ្វើអ្វីត្រឹមត្រូវ អ្នកអភិវឌ្ឍដែលប្រុងប្រយ័ត្នសម្រេច អ្វីដែលត្រូវទម្លាក់ ក្នុងកូដ។ មានយុទ្ធសាស្ត្រទូទៅបី៖
- Trim (កាត់)៖ ទម្លាក់វេនចាស់បំផុត។ សាមញ្ញ ប៉ុន្តែការពិតចាស់បាត់ទៅ។
- Summarize (សង្ខេប)៖ ជំនួសវេនចាស់ដោយសេចក្តីសង្ខេបខ្លី។ រក្សាខ្លឹមសារ ប៉ុន្តែសេចក្តីសង្ខេបអាចបាត់ព័ត៌មានលម្អិត ឬបន្ថែមកំហុស។
- Retrieve (ទាញយក)៖ រក្សាទុកអ្វីៗទាំងអស់នៅខាងក្រៅ ហើយទាញយកតែផ្នែកដែលពាក់ព័ន្ធ (ផ្នែកទី 12)។ មានផ្នែកដែលត្រូវគ្រប់គ្រងច្រើនជាង។
អ្នកអាចផ្សំពួកវាបាន។ តោះសាងសង់ពីរដំបូង។
7. Trimming៖ រក្សាសារ System និងវេនថ្មីបំផុត
ច្បាប់៖ រក្សាសារ system ជានិច្ច (វាមានច្បាប់សម្រាប់ការសន្ទនាទាំងមូល) បន្ទាប់មករក្សា វេន ថ្មីបំផុត ដែលសមក្នុងថវិកា ហើយទម្លាក់វេនចាស់បំផុត។
// src/context.js (part 3)
// Keep the system message and as many of the NEWEST turns as fit.
export function trimHistory(messages, maxTokens) {
const system = messages.filter((m) => m.role === "system");
const rest = messages.filter((m) => m.role !== "system");
let budget = maxTokens - totalTokens(system);
const kept = [];
for (let i = rest.length - 1; i >= 0; i--) {
const cost = messageTokens(rest[i]);
if (cost > budget) break;
kept.unshift(rest[i]);
budget -= cost;
}
return [...system, ...kept];
}
យើងដើរថយក្រោយពីសារថ្មីបំផុត បន្ថែមសារ ខណៈដែលវាសម ហើយឈប់នៅសារដំបូងដែលមិនសម។
unshift ដាក់នីមួយៗនៅខាងមុខ ដូច្នេះលំដាប់ដើមត្រូវបានរក្សា។
ឥឡូវមើលវាភ្លេច។ chat នេះមានឈ្មោះនៅដើម ហើយបន្ទាប់មកការនិយាយលេងបន្តិចបន្តួច៖
import { messageTokens, totalTokens, trimHistory, fakeModel } from "./src/context.js";
const chat = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "My name is Sokha." },
{ role: "assistant", content: "Noted." },
{ role: "user", content: "I like iced tea." },
{ role: "assistant", content: "Noted." },
{ role: "user", content: "The coffee shop is quiet in the morning." },
{ role: "assistant", content: "Noted." },
{ role: "user", content: "What is my name?" },
];
console.log(chat.map(messageTokens));
// [
// 11, 9, 6, 8,
// 6, 14, 6, 8
// ]
console.log(totalTokens(chat));
// 68
const trimmed = trimHistory(chat, 30);
console.log(totalTokens(trimmed), trimmed.length);
// 25 3
for (const m of trimmed) console.log(m.role.padEnd(9), m.content);
// system You are a helpful assistant.
// assistant Noted.
// user What is my name?
console.log(fakeModel(trimmed));
// I don't know your name. You have not told me.
console.log(fakeModel(trimHistory(chat, 200)));
// Your name is Sokha.
ជាមួយថវិកា 30 token មានតែសារ system និងសារពីរចុងក្រោយប៉ុណ្ណោះដែលនៅរស់។ បន្ទាត់ "My name is Sokha." ត្រូវបានទម្លាក់ ដូច្នេះម៉ូដែលនិយាយដោយស្មោះត្រង់ថាវាមិនដឹង។ ជាមួយថវិកា 200 អ្វីៗទាំងអស់សម ហើយវាឆ្លើយត្រូវ។
ចំណុចពីរដែលត្រូវកត់សម្គាល់៖
- ម៉ូដែលមិនបាន "ភ្លេច" ទេ។ កូដរបស់អ្នកបានបោះព័ត៌មានចោល។ នៅពេល chatbot ភ្លេចអ្វីពីយូរមកហើយ នោះជាធម្មតាជាការសម្រេចចិត្តរចនាក្នុងកម្មវិធី មិនមែនកំហុសរបស់ម៉ូដែលទេ។
- Trimming អាចចាប់ផ្តើមនៅកណ្តាលគូ។ chat ដែលបានកាត់របស់យើងចាប់ផ្តើមដោយសារ
assistantភ្លាមបន្ទាប់ពីសារ system។ API ខ្លះចង់ឲ្យសារមិនមែន system ដំបូងជាសារuserដូច្នេះកំណែផលិតកម្មជារឿយៗទម្លាក់វេន assistant នាំមុខនោះដែរ។ នោះជាលំហាត់ល្អ (លេខ 3 ខាងក្រោម)។
8. ការសង្ខេបវេនចាស់
Trimming ភ្លេចការពិតចាស់ទាំងស្រុង។ គំនិតទន់ភ្លន់ជាង៖ ជំនួសវេនចាស់ដោយ សេចក្តីសង្ខេបខ្លី ហើយរក្សាវេនថ្មីបំផុតដូចដើម។ ក្នុងកម្មវិធីពិត អ្នកសុំម៉ូដែលខ្លួនឯងឲ្យ សរសេរសេចក្តីសង្ខេប ("សង្ខេបការសន្ទនានេះក្នុង 5 បន្ទាត់ រក្សាឈ្មោះ លេខ និងការសម្រេចចិត្ត")។ នៅទីនេះយើងសរសេរ ឧបករណ៍ជំនួស ដែលបង្ហាញតែរូបរាងនៃគំនិត៖
// src/context.js (part 4)
// Placeholder: a real app would ask a model to write this summary.
// Here we just note how many old messages were folded away.
export function summarizeOld(messages, keepLast) {
const system = messages.filter((m) => m.role === "system");
const rest = messages.filter((m) => m.role !== "system");
const old = rest.slice(0, Math.max(0, rest.length - keepLast));
const recent = rest.slice(-keepLast);
if (old.length === 0) return messages;
const note = {
role: "system",
content: `Summary of earlier chat: ${old.length} messages (replace with a real summary).`,
};
return [...system, note, ...recent];
}
import { summarizeOld } from "./src/context.js";
// (reuse the 8-message `chat` array from section 7)
const folded = summarizeOld(chat, 2);
for (const m of folded) console.log(m.role.padEnd(9), m.content);
// system You are a helpful assistant.
// system Summary of earlier chat: 5 messages (replace with a real summary).
// assistant Noted.
// user What is my name?
សារចាស់ប្រាំសារបានក្លាយជាកំណត់ចំណាំខ្លីមួយ។ ប្រសិនបើសេចក្តីសង្ខេបពិតនិយាយថា "ឈ្មោះអ្នកប្រើគឺ Sokha ហើយពួកគេចូលចិត្តតែទឹកកក" ម៉ូដែលនៅតែអាចឆ្លើយសំណួរឈ្មោះ បន្ទាប់ពីវេនចាស់បាត់ទៅ។
សេចក្តីសង្ខេបមានភាពទន់ខ្សោយដោយស្មោះត្រង់៖ ព័ត៌មានលម្អិតបាត់ អ្នកសង្ខេបជាម៉ូដែល ដែលអាចចាំការពិតខុស (ហើយការពិតខុសក្នុងសេចក្តីសង្ខេបត្រូវបានទុកចិត្តបន្ទាប់មក) ហើយ ការសង្ខេបខ្លួនវាក៏ជា request ដែលអ្នកបង់ថ្លៃដែរ។
ទម្រង់ទូទៅមួយគឺរក្សាការពិតសំខាន់ៗក្នុងបញ្ជី "profile" តូចដាច់ដោយឡែក ដែលកម្មវិធីរបស់អ្នកកែសម្រួលយ៉ាងច្បាស់លាស់ (ឈ្មោះ ភាសា ចំណូលចិត្ត) ហើយបញ្ចូលក្នុង សារ system ជានិច្ច។ នោះគួរឲ្យទុកចិត្តជាងការសង្ឃឹមថាសេចក្តីសង្ខេបរក្សាពួកវា។
9. ថ្លៃដើមកើនឡើងតាមប្រវត្តិ
ម៉ូដែល hosted ភាគច្រើនគិតថ្លៃតាម token ដោយមានតម្លៃសម្រាប់ token ដែលអ្នក ផ្ញើ (input) និង token ដែលម៉ូដែល សរសេរ (output)។ តម្លៃខុសគ្នាតាមម៉ូដែល ហើយផ្លាស់ប្តូរ។ ក្នុងជំពូកនេះយើងប្រើឧទាហរណ៍ប្រឌិត 1.00 ក្នុងមួយលាន input token គ្រាន់តែដើម្បីបង្ហាញរូបរាងនៃថ្លៃដើម។
នេះជាអន្ទាក់៖ ដោយសារគ្រប់វេនផ្ញើប្រវត្តិពេញលេញម្តងទៀត token ដែលអ្នកបង់ថ្លៃ កើនឡើង លឿនជាងចំនួនវេន។ ប្រសិនបើរាល់វេនបន្ថែមប្រហែល 200 token នោះ៖
- វេនទី 1 ផ្ញើ 200 token
- វេនទី 2 ផ្ញើ 400 (200 ចាស់បូក 200 ថ្មី)
- វេនទី 3 ផ្ញើ 600
- ហើយវេនទី
tផ្ញើ200 * t។
បូកវាទាំងអស់ ហើយសរុបលើ T វេនគឺ 200 * (1 + 2 + ... + T) ដែលប្រហែល
200 * T * T / 2។ វាកើនឡើងដូច T^2 ជារូបរាង quadratic ដូចគ្នាពី
Big-O Notation។
ការបង្កើនប្រវែង chat ទ្វេដង ធ្វើឲ្យថ្លៃដើម input កើនឡើងប្រហែលបួនដង។
// src/context.js (part 5)
// Total input tokens you pay for over a whole conversation, because
// every turn resends everything before it.
export function resendCost(tokensPerTurn, turns, pricePerMillion) {
let tokens = 0;
for (let t = 1; t <= turns; t++) tokens += tokensPerTurn * t;
return { tokens, cost: (tokens / 1_000_000) * pricePerMillion };
}
import { resendCost } from "./src/context.js";
for (const turns of [1, 10, 50, 100]) {
const r = resendCost(200, turns, 1); // example price: 1.00 per million
console.log(turns, r.tokens, r.cost.toFixed(4));
}
// 1 200 0.0002
// 10 11000 0.0110
// 50 255000 0.2550
// 100 1010000 1.0100
chat 10 វេនបានផ្ញើ input token សរុប 11,000 មិនមែន 2,000 ទេ។ chat 100 វេនផ្ញើ លើសពីមួយលាន ដែលមានវេន ច្រើនជាងដប់ដង ជាង chat 10 វេន ប៉ុន្តែ token ច្រើនជាងប្រហែល កៅសិបដង។ chat ពិតខុសគ្នាបន្តិច (វេនមានប្រវែងខុសគ្នា ហើយ output ថ្លៃបន្ថែម) ប៉ុន្តែរូបរាងនោះជាការពិត។
ប្រវត្តិវែងក៏ធ្វើឲ្យការឆ្លើយតប យឺតជាង ដែរ ពីព្រោះម៉ូដែលត្រូវអានវាទាំងអស់ មុនពេលវាសរសេរពាក្យដំបូង។ ដូច្នេះ trimming និង summarizing មិនមែនគ្រាន់តែអំពីការសម្រួល ឲ្យសមនឹង window ទេ៖ វាសន្សំលុយ និងពេលវេលាផងដែរ។ ការរក្សា prompt ឲ្យខ្លីគឺជាជំនាញមួយ។
10. Lost in the Middle
ប្រសិនបើម៉ូដែលអាចដាក់ 100 ទំព័របាន តើវានឹងប្រើទាំង 100 ទំព័រស្មើគ្នាល្អទេ? អ្នកស្រាវជ្រាវ និងអ្នកអភិវឌ្ឍបានសង្កេតឃើញជាទូទៅថា មិនតែងតែទេ។ ការពិតដែលដាក់នៅដើមបំផុត ឬចុងបំផុតនៃ prompt វែង ជារឿយៗត្រូវបានរកឃើញដោយភាពជឿជាក់ជាង ការពិតដែលកប់នៅកណ្តាល។ នេះហៅថាឥទ្ធិពល lost in the middle។
សូមប្រុងប្រយ័ត្នក្នុងរបៀបនិយាយវា។ វាជា និន្នាការដែលសង្កេតឃើញជាទូទៅ មិនមែនច្បាប់ ធម្មជាតិទេ។ វាខុសគ្នាពីម៉ូដែលមួយទៅមួយ វាអាចប្រសើរឡើងនៅពេលម៉ូដែលប្រសើរឡើង ហើយវាអាស្រ័យលើកិច្ចការ។ ប៉ុន្តែវាជារឿងទូទៅគ្រប់គ្រាន់ដែលអ្នកអភិវឌ្ឍដែលប្រុងប្រយ័ត្ន រៀបចំផែនការសម្រាប់វា៖
- ដាក់ការណែនាំសំខាន់បំផុតនៅដើម ហើយពិចារណាធ្វើម្តងទៀតនូវការរំលឹកខ្លីនៅចុង។
- កុំដាក់អ្វីៗទាំងអស់ "ក្នុងករណីចាំបាច់"។ prompt ដែលមានបីកថាខណ្ឌពាក់ព័ន្ធ ជាធម្មតាល្អជាង prompt ដែលមានហាសិបទំព័រដែលចម្លើយត្រូវបានលាក់។
- សាកល្បងជាមួយទិន្នន័យរបស់អ្នកផ្ទាល់។ ដាក់ការពិតដែលស្គាល់នៅកន្លែងផ្សេងៗ ហើយមើលថាតើម៉ូដែលរបស់អ្នករកឃើញទេ។ ការវាយតម្លៃដែលអ្នកដំណើរការខ្លួនឯង មានតម្លៃជាង ការអះអាងណាមួយក្នុងអត្ថបទ។
មេរៀនគឺផ្ទុយពី "window ធំ ដូច្នេះចាក់អ្វីៗទាំងអស់ចូល"៖ ជ្រើសរើសអ្វីដែលចូលដោយចេតនា។
11. Prompt Caching៖ គំនិត
សូមមើលរង្វិលជុំផ្ញើម្តងទៀតក្នុងផ្នែកទី 3 ម្តងទៀត។ វេនទី 2 ផ្ញើសារដំបូងដូចគ្នានឹងវេនទី 1 បូកបន្ថែមបន្តិច។ ដើមនៃ prompt គឺដូចគ្នារាល់ពេល។ ការអានអត្ថបទដូចគ្នាម្តងហើយម្តងទៀត គឺខ្ជះខ្ជាយ ហើយអ្នកផ្តល់សេវាខ្លះផ្តល់ជូន prompt caching ដើម្បីជៀសវាងវា។
គំនិតក្នុងកម្រិតគំនិត៖ អ្នកផ្តល់សេវាចងចាំលទ្ធផលនៃការដំណើរការ prefix នៃ prompt របស់អ្នក (ផ្នែកដើម)។ ប្រសិនបើ request បន្ទាប់របស់អ្នកចាប់ផ្តើមដោយ prefix ដូចគ្នាបេះបិទ វាអាចប្រើការងារនោះឡើងវិញជំនួសឲ្យការធ្វើម្តងទៀត ជារឿយៗក្នុងតម្លៃទាបជាង និងលឿនជាង។ ព័ត៌មានលម្អិត តម្លៃ និងច្បាប់ខុសគ្នាតាមអ្នកផ្តល់សេវា ដូច្នេះសូមអានឯកសាររបស់ពួកគេ។ យើងចង់ឲ្យអ្នកដឹងតែគំនិតប៉ុណ្ណោះ ពីព្រោះវាផ្លាស់ប្តូររបៀបដែលអ្នកគួររចនា prompt។
យើងអាចមើលឃើញអ្វីដែល "prefix ដូចគ្នា" មានន័យ ដោយកូដពីរបីបន្ទាត់៖
import { totalTokens } from "./src/context.js";
const turn1 = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "My name is Sokha." },
];
const turn2 = [
...turn1,
{ role: "assistant", content: "Noted." },
{ role: "user", content: "I like iced tea." },
];
function sharedPrefix(a, b) {
let i = 0;
while (i < a.length && i < b.length &&
a[i].content === b[i].content && a[i].role === b[i].role) i++;
return i;
}
console.log(sharedPrefix(turn1, turn2), turn2.length);
// 2 4
console.log(totalTokens(turn1), totalTokens(turn2));
// 20 34
វេនទី 2 ចាប់ផ្តើមដោយ 2 សារ (20 token) នៃវេនទី 1។ cache អាចប្រើពួកវាឡើងវិញ ហើយដំណើរការតែ 14 token ថ្មីប៉ុណ្ណោះ។ សម្រាប់ system prompt វែង ឬឯកសារធំដែលបានបិទភ្ជាប់ នេះសំខាន់ខ្លាំងណាស់។
ច្បាប់រចនាដែលតាមក្រោយ៖ ដាក់ផ្នែកថេរមុន ហើយផ្នែកដែលផ្លាស់ប្តូរក្រោយ។ ការណែនាំ ឧទាហរណ៍ និងឯកសារយោងទៅនៅដើម សំណួរថ្មីរបស់អ្នកប្រើទៅនៅចុង។ ហើយកុំផ្លាស់ប្តូរ ដើមដោយគ្មានមូលហេតុ៖ សូម្បីតែការកែតូចមួយ (timestamp ID ចៃដន្យ) នៅខាងលើ prompt ក៏បំបែក prefix រួម ហើយ cache មិនអាចជួយបានទេ។
12. Long Context ទល់នឹងការស្វែងរកផ្នែកត្រឹមត្រូវ
នៅពេលឯកសាររបស់អ្នកធំជាង window ឬអ្នកមិនចង់បង់ថ្លៃផ្ញើវាម្តងទៀតរាល់វេន មានវិធីសាស្ត្រទូលំទូលាយពីរ៖
- ដាក់អ្វីៗទាំងអស់ចូល។ ប្រើម៉ូដែលដែលមាន window ធំ ហើយបិទភ្ជាប់អ្វីៗទាំងអស់។ សាមញ្ញ ប៉ុន្តែថ្លៃ យឺតជាង និងប្រឈមនឹងបញ្ហាកណ្តាល។
- ទាញយកតែអ្វីដែលពាក់ព័ន្ធ។ រក្សាឯកសារនៅខាងក្រៅម៉ូដែល។ សម្រាប់រាល់សំណួរ ស្វែងរកបំណែកពីរបីដែលសំខាន់ ហើយដាក់តែពួកវាក្នុង prompt។ នេះប្រើគំនិត vector-similarity ដែលអ្នកបានឃើញក្នុង Embedding និង Attention។
វិធីសាស្ត្រទីពីរហៅថា retrieval-augmented generation ជាទូទៅកាត់ជា RAG។ វាជារបៀបដែលផលិតផល "chat ជាមួយឯកសាររបស់អ្នក" ភាគច្រើនដំណើរការ ហើយវាក៏ជាការការពារល្អ ប្រឆាំងនឹងការប្រឌិតរឿង ដែលជាប្រធានបទនៃ ហេតុអ្វីម៉ូដែល Hallucinate។ ស៊េរីក្រោយមួយនឹងគ្របដណ្តប់ការសាងសង់វាយ៉ាងពិតប្រាកដ។ គ្មានវិធីសាស្ត្រណាតែងតែល្អជាងទេ៖ prompt ខ្លីដែលបានជ្រើសរើសល្អគឺថោក និងមុត ហើយ window វែងងាយស្រួលនៅពេលអ្នកពិតជាត្រូវការ ឯកសារទាំងមូលក្នុងពេលតែមួយ។
13. រូបមន្ត៖ Module context
src/context.js របស់អ្នកឥឡូវមានប្រាំផ្នែកពីជំពូកនេះ តាមលំដាប់នេះ៖ fakeModel
(ផ្នែកទី 2) ឧបករណ៍រាប់ token បី (ផ្នែកទី 5) trimHistory (ផ្នែកទី 7)
summarizeOld (ផ្នែកទី 8) និង resendCost (ផ្នែកទី 9)។ គ្រប់ function ត្រូវបាន
export ហើយឯកសារគ្មាន import និងគ្មាន dependency ទេ។
Export ជាមួយអ្វីដែលនីមួយៗធ្វើ៖
| Function | គោលបំណង |
|---|---|
estimateTokens(text) | ចំនួន token ប្រហាក់ប្រហែល (ប្រហែល 4 តួអក្សរក្នុងមួយ) |
messageTokens(message) | token ប៉ាន់ស្មាននៃសារមួយ ជាមួយក្រដាសរុំ |
totalTokens(messages) | token ប៉ាន់ស្មាននៃការសន្ទនាទាំងមូល |
fakeModel(messages) | ម៉ូដែលក្មេងលេង stateless ដែលដឹងតែអ្វីដែលអ្នកប្រគល់ឲ្យ |
trimHistory(messages, maxTokens) | រក្សាសារ system បូកវេនថ្មីបំផុតដែលសម |
summarizeOld(messages, keepLast) | ឧបករណ៍ជំនួស៖ បត់វេនចាស់ទៅជាកំណត់ចំណាំ |
resendCost(tokensPerTurn, turns, pricePerMillion) | token input សរុប និងថ្លៃដើមលើ chat មួយ |
សូមចាំថា module នេះជាអ្វី និងមិនមែនជាអ្វី៖ fakeModel ជាប្រដាប់ក្មេងលេង ហើយ
estimateTokens ជាការទាយ។ តម្លៃពិតគឺ រូបរាង នៃកូដ (array នៃសារ ថវិកា
ការកាត់ពីចុងចាស់ កំណត់ចំណាំសង្ខេប) ដែលបន្តប្រើបានដដែលជាមួយ API ពិតក្នុងស៊េរីបន្ទាប់។
14. សាកល្បងដោយខ្លួនឯង
- បង្ហាញថាវា stateless។ ហៅ
fakeModelពីរដងជាប់គ្នាជាមួយ array ពីរសារដូចគ្នា។ បន្ទាប់មកហៅវាម្តងទៀតបន្ទាប់ពីរុញសារ user ថ្មីចូល array ផ្សេង។ ពន្យល់ក្នុងមួយប្រយោគថាហេតុអ្វីការហៅពីរដំបូងមិនអាចប៉ះពាល់គ្នាទៅវិញទៅមកបាន។ - ថវិកា window។ ជ្រើសរើស window គំរូ 4,000 token។ ដោយប្រើ
estimateTokensតើcorpusប៉ុន្មានច្បាប់ចម្លងសម? តើនៅសល់ token ប៉ុន្មាន សម្រាប់ចម្លើយ ប្រសិនបើអ្នកបិទភ្ជាប់ 10 ច្បាប់ចម្លង? - ជួសជុល assistant នាំមុខ។ បន្ទាប់ពី
trimHistoryប្រសិនបើសារមិនមែន system ដំបូង មកពី"assistant"ទម្លាក់វា។ សរសេរtrimHistoryStrict(messages, maxTokens)ហើយបង្ហាញថាវាមិនដែលចាប់ផ្តើមដោយវេន assistant ទេ។ - រក្សាសំណួរដំបូងជានិច្ច។ ផ្លាស់ប្តូរ
trimHistoryដើម្បីឲ្យវាក៏រក្សា សារ user ដំបូង (ជារឿយៗជាការពិពណ៌នាកិច្ចការ) បូកវេនថ្មីបំផុតដែលសម។ តើបញ្ហាថ្មីអ្វីលេចឡើងនៅពេលកណ្តាលបាត់? ភ្ជាប់ចម្លើយរបស់អ្នកទៅនឹងផ្នែកទី 10។ - គូសវិក្កយបត្រ។ ដោយប្រើ
resendCostរកថាតើវេន 200 token ប៉ុន្មាន ធ្វើឲ្យ input សរុបលើសពី 1,000,000 token។ បន្ទាប់មកផ្លាស់ប្តូរទំហំវេនទៅ 100 ហើយរកម្តងទៀត។ តើការកាត់ទំហំវេនពាក់កណ្តាលកាត់ចំនួនវេនពាក់កណ្តាលទេ? ហេតុអ្វីមិនមែន? - អ្នកសង្ខេបល្អជាង។ ជំនួសឧបករណ៍ជំនួសក្នុង
summarizeOldដោយកំណែដែលប្រមូលរាល់my name is XនិងI like Xពីសារ user ចាស់ ចូលក្នុងកំណត់ចំណាំសង្ខេប ដូច្នេះfakeModelនៅតែអាចឆ្លើយបន្ទាប់ពី trimming។ តើវិធីសាស្ត្រនេះនឹងខកខានការពិតប្រភេទណា?
15. សំណួរដែលអ្នកនឹងត្រូវគេសួរ
តើ AI បែប ChatGPT ចងចាំខ្ញុំរវាងការសន្ទនាទេ? ខ្លួនម៉ូដែលមិនចងចាំទេ។ រាល់ request ដាច់ដោយឡែក។ ផលិតផលខ្លះបន្ថែមមុខងារ "ការចងចាំ" ផ្ទាល់ខ្លួន៖ កម្មវិធីរក្សាទុកកំណត់ចំណាំអំពីអ្នក ហើយដាក់ក្នុង prompt លើកក្រោយ។ នោះជាមុខងាររបស់កម្មវិធី ហើយអ្នកជាធម្មតាអាចមើល និងលុបវាបាន។ ប្រសិនបើកម្មវិធីមិនធ្វើដូច្នេះ chat ថ្មីចាប់ផ្តើមពីសូន្យ។
ហេតុអ្វី AI ភ្លេចអ្វីពីមុននៅក្នុង chat វែង? ជាធម្មតាពីព្រោះកម្មវិធីបានទម្លាក់ ឬបង្រួមសារចាស់ ដើម្បីសមនឹង context window ឬសន្សំលុយ ឬពីព្រោះម៉ូដែលមិនបានផ្តល់ទម្ងន់ល្អដល់ផ្នែកនោះនៃ prompt វែងខ្លាំង។ ម៉ូដែលមិនបាន "បាត់បង់" អ្វីទេ៖ វាមិនដែលកាន់វាតាំងពីដំបូង គ្រាន់តែអានអត្ថបទអ្វីដែលត្រូវបានផ្ញើ។
តើ context window ជាអ្វីក្នុងពាក្យសាមញ្ញ? វាជាទំហំនៃតុ។ វាជាអត្ថបទច្រើនបំផុត រាប់ជា token ដែលម៉ូដែលអាចអានក្នុង request មួយ រួមទាំងសំណួររបស់អ្នក និងចម្លើយរបស់វាផ្ទាល់។ ក្រដាសដែលមិនសមលើតុមិនអាចត្រូវបានប្រើទេ។
ហេតុអ្វីការសន្ទនាវែងថ្លៃជាង? ពីព្រោះសារថ្មីនីមួយៗផ្ញើប្រវត្តិទាំងមូលម្តងទៀត ហើយអ្នកបង់ថ្លៃសម្រាប់ token ទាំងនោះរាល់ពេល។ ថ្លៃដើមកើនឡើងលឿនជាងចំនួនសារ។ prompt វែងជាងក៏យឺតជាងក្នុងការចាប់ផ្តើមដែរ។ ការចាប់ផ្តើម chat ថ្មីសម្រាប់ប្រធានបទថ្មីគឺថោកជាង ហើយជារឿយៗផ្តល់ចម្លើយល្អជាង។
តើ context window ធំជាងតែងតែល្អជាងទេ? ទេ។ វាអនុញ្ញាតឲ្យអ្នកដាក់បានច្រើនជាង ប៉ុន្តែវាថ្លៃជាង ដំណើរការយឺតជាង ហើយម៉ូដែល មិនតែងតែប្រើ prompt វែងទាំងអស់ស្មើគ្នាទេ។ ជារឿយៗការរចនាល្អជាងគឺ prompt ខ្លីដែលមាន តែផ្នែកពាក់ព័ន្ធ។ សាកល្បងជាមួយទិន្នន័យរបស់អ្នកផ្ទាល់។
តើម៉ូដែលអាចរៀនពី chat របស់ខ្ញុំ ហើយឆ្លាតជាងមុនទេ? មិនមែនក្នុង chat ទេ។ weight មិនផ្លាស់ប្តូរនៅពេលអ្នកនិយាយជាមួយវាទេ។ ម៉ូដែលអាចធ្វើតាមអ្វីដែលអ្នកនិយាយក្នុងការសន្ទនា ពីព្រោះវាស្ថិតក្នុង prompt ប៉ុន្តែនៅពេលអត្ថបទនោះបាត់ ឥទ្ធិពលក៏បាត់ដែរ។ ក្រុមហ៊ុនអាចប្រើការសន្ទនាក្នុងពេលក្រោយ ដើម្បីហ្វឹកហ្វឺនកំណែថ្មី អាស្រ័យលើគោលនយោបាយរបស់ពួកគេ ដូច្នេះសូមអាន ការកំណត់ភាពឯកជនមុនពេលបិទភ្ជាប់អ្វីដែលរសើប។
តើ prompt caching ជាអ្វី? វាជាមុខងាររបស់អ្នកផ្តល់សេវាដែលប្រើការងារឡើងវិញពីដើមដូចគ្នាបេះបិទនៃ prompt របស់អ្នក ដូច្នេះផ្នែកដែលធ្វើម្តងទៀត (សារ system វែង ឬឯកសារធំ) ថោកជាង និងលឿនជាងនៅលើកក្រោយ។ ដើម្បីទទួលបានអត្ថប្រយោជន៍ សូមរក្សាអត្ថបទថេរនៅដើម ហើយសំណួរថ្មីនៅចុង។ របៀបតម្លៃ និងរយៈពេលដែលវាមានអាស្រ័យលើអ្នកផ្តល់សេវា។
ទៅបន្តទៀត។ ឥឡូវអ្នកដឹងថា chat គឺជា array នៃសារដែលកូដរបស់អ្នកផ្ញើម្តងទៀត កាត់ និងបង់ថ្លៃ។ ប៉ុន្តែអ្វីដែលនៅ ខាងក្នុង សារទាំងនោះ ហើយអ្នកណាមានសិទ្ធិនិយាយអ្វី? ជំពូកបន្ទាប់ Prompt, Role និង Structured Output គ្របដណ្តប់ role system និង user, ឧទាហរណ៍ few-shot, ការទទួលបាន JSON ដែលអាចទុកចិត្តបានពី ម៉ូដែល និងហានិភ័យសន្តិសុខធំបំផុតទាំងអស់៖ prompt injection។
ទិញកាហ្វេជូនខ្ញុំ
ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!