Czym jest token?
Około 4 znaki tekstu. “Witaj, świecie!” ≈ 3 tokeny
Zrozumienie i optymalizacja użycia tokenów jest kluczowa dla efektywnego rozwoju wspomaganego przez AI. Ten przewodnik obejmuje zaawansowane strategie zarządzania tokenami, zmniejszania kosztów i maksymalizacji wartości każdej interakcji z AI.
Czym jest token?
Około 4 znaki tekstu. “Witaj, świecie!” ≈ 3 tokeny
Okno kontekstu
Maksymalne tokeny na żądanie (8K do 1M+ w zależności od modelu)
Struktura cenowa
Opłata za 1K tokenów zarówno dla wejścia jak i wyjścia
Limity tokenów
Ograniczenia częstotliwości i miesięczne kwoty różnią się według planu
// Narzędzia do szacowania tokenówclass TokenCalculator { // Przybliżone szacowanie: 1 token ≈ 4 znaki private readonly CHARS_PER_TOKEN = 4;
estimateTokens(text: string): number { // Dokładniejsze szacowanie uwzględniające: // - Białe znaki i interpunkcję // - Narzut składni kodu // - Znaki specjalne
const baseTokens = text.length / this.CHARS_PER_TOKEN; const codeMultiplier = this.getCodeMultiplier(text);
return Math.ceil(baseTokens * codeMultiplier); }
private getCodeMultiplier(text: string): number { const indicators = { hasCode: /```[\s\S]*```/.test(text), hasJSON: /\{[\s\S]*\}/.test(text), hasSpecialChars: /[^\x00-\x7F]/.test(text), hasIndentation: /^\s{2,}/m.test(text) };
let multiplier = 1.0; if (indicators.hasCode) multiplier *= 1.2; if (indicators.hasJSON) multiplier *= 1.15; if (indicators.hasSpecialChars) multiplier *= 1.1; if (indicators.hasIndentation) multiplier *= 1.05;
return multiplier; }}
// Inteligentne warstwowanie kontekstuclass ContextLayerManager { private layers = { immediate: 2000, // Obecny plik + bezpośrednie zależności relevant: 5000, // Powiązane pliki w module extended: 10000, // Szerszy kontekst bazy kodu reference: 20000 // Dokumentacja i przykłady };
async buildOptimalContext(task: Task): Promise<Context> { const context = new Context();
// Zacznij od natychmiastowego kontekstu context.add(await this.getImmediateContext(task));
// Dodaj warstwy na podstawie złożoności zadania if (task.complexity > 5) { context.add(await this.getRelevantContext(task)); }
if (task.requiresArchitecturalKnowledge) { context.add(await this.getExtendedContext(task)); }
// Zawsze zostaw miejsce na odpowiedź const responseBuffer = this.estimateResponseSize(task); return this.pruneContext(context, responseBuffer); }
private pruneContext(context: Context, responseBuffer: number): Context { const maxTokens = this.getMaxTokensForModel() - responseBuffer;
if (context.tokenCount <= maxTokens) { return context; }
// Inteligentne przycinanie return this.intelligentPrune(context, maxTokens); }}
// Usuń niepotrzebne tokeny z kontekstu koduclass CodeMinifier { minifyForContext(code: string): string { // Usuń komentarze (oprócz JSDoc) code = code.replace(/\/\/(?!\/)[^\n]*/g, ''); code = code.replace(/\/\*(?![\*!])[^*]*\*+(?:[^/*][^*]*\*+)*\//g, '');
// Usuń nadmiarowe białe znaki code = code.replace(/\s+/g, ' '); code = code.replace(/\s*([{}();,])\s*/g, '$1');
// Usuń console.logs w kontekście code = code.replace(/console\.(log|warn|error)\([^)]*\);?/g, '');
return code.trim(); }
preserveStructure(code: string): string { // Zachowaj strukturę ale minimalizuj tokeny return code .split('\n') .map(line => { // Zachowaj strukturę wcięć const indent = line.match(/^\s*/)[0]; const content = line.trim();
// Pomiń puste linie if (!content) return '';
// Minimalizuj ale zachowaj czytelność return indent + content; }) .filter(Boolean) .join('\n'); }}
// Kompresuj przez wyodrębnienie znaczenia semantycznegoclass SemanticCompressor { async compressContext(files: File[]): Promise<CompressedContext> { const signatures = []; const relationships = [];
for (const file of files) { // Wyodrębnij tylko sygnatury i typy const ast = await this.parseFile(file);
signatures.push({ file: file.path, exports: this.extractExports(ast), imports: this.extractImports(ast), types: this.extractTypes(ast) });
relationships.push(this.extractRelationships(ast)); }
return { signatures, relationships, summary: this.generateSummary(signatures, relationships) }; }}
// Używaj odwołań zamiast pełnej zawartościclass ReferenceCompressor { compressWithReferences(context: Context): CompressedContext { const seen = new Map<string, string>(); const compressed = [];
for (const item of context.items) { const hash = this.hashContent(item.content);
if (seen.has(hash)) { // Zastąp odwołaniem compressed.push({ type: 'reference', ref: seen.get(hash), path: item.path }); } else { // Pierwsze wystąpienie const id = this.generateId(); seen.set(hash, id);
compressed.push({ type: 'definition', id, content: item.content, path: item.path }); } }
return { compressed, savings: this.calculateSavings(context, compressed) }; }}
// Wybierz optymalny model na podstawie wymagań tokenowychclass ModelSelector { private models = { 'claude-4-haiku': { contextWindow: 8192, costPer1kInput: 0.0003, costPer1kOutput: 0.0015, speed: 'fast', quality: 'good' }, 'claude-4-sonnet': { contextWindow: 200000, costPer1kInput: 0.003, costPer1kOutput: 0.015, speed: 'medium', quality: 'excellent' }, 'claude-4.1-opus': { contextWindow: 200000, costPer1kInput: 0.015, costPer1kOutput: 0.075, speed: 'slow', quality: 'best' }, 'gemini-2.5-pro': { contextWindow: 1000000, costPer1kInput: 0.002, costPer1kOutput: 0.008, speed: 'fast', quality: 'excellent' } };
selectOptimalModel(context: Context, task: Task): ModelChoice { const factors = { contextSize: context.tokenCount, taskComplexity: task.complexity, qualityRequired: task.qualityRequirement, budgetConstraint: task.maxCost, speedRequirement: task.urgency };
// Filtruj według okna kontekstu const compatible = Object.entries(this.models) .filter(([_, model]) => model.contextWindow >= factors.contextSize);
// Oceniaj każdy model const scored = compatible.map(([name, model]) => ({ name, model, score: this.scoreModel(model, factors) }));
// Zwróć najlepsze dopasowanie return scored.sort((a, b) => b.score - a.score)[0]; }}
Typ zadania | Zalecany model | Strategia kontekstu | Oczekiwane tokeny |
---|---|---|---|
Proste edycje | Haiku | Minimalna (2-3K) | 500-1K wyjścia |
Rozwój funkcji | Sonnet | Umiarkowana (10-20K) | 2-5K wyjścia |
Refaktoryzacja | Sonnet/Opus | Rozszerzona (20-50K) | 5-10K wyjścia |
Architektura | Opus/Gemini | Pełna (50K+) | 10K+ wyjścia |
Analiza błędów | Gemini | Ukierunkowana (30K) | 3-5K wyjścia |
// Monitoruj użycie tokenów w sesjachclass TokenMonitor { private usage = new Map<string, TokenUsage>();
async trackRequest(request: AIRequest, response: AIResponse) { const usage: TokenUsage = { timestamp: new Date(), model: request.model, inputTokens: await this.countTokens(request.prompt), outputTokens: await this.countTokens(response.content), cost: this.calculateCost(request.model, inputTokens, outputTokens), task: request.metadata.task, user: request.metadata.user };
this.recordUsage(usage); this.checkAlerts(usage); }
async generateReport(period: Period): Promise<UsageReport> { const usage = this.getUsageForPeriod(period);
return { totalTokens: usage.reduce((sum, u) => sum + u.inputTokens + u.outputTokens, 0), totalCost: usage.reduce((sum, u) => sum + u.cost, 0), byModel: this.groupByModel(usage), byTask: this.groupByTask(usage), byUser: this.groupByUser(usage), trends: this.analyzeTrends(usage), recommendations: this.generateRecommendations(usage) }; }}
// Analityka dla optymalizacji tokenówclass TokenAnalytics { analyzePatterns(usage: TokenUsage[]): AnalysisResult { return { inefficientPatterns: this.findInefficiencies(usage), optimizationOpportunities: this.findOptimizations(usage), costSavingPotential: this.calculateSavings(usage), userBehaviors: this.analyzeUserPatterns(usage) }; }
private findInefficiencies(usage: TokenUsage[]): Inefficiency[] { const inefficiencies = [];
// Duży kontekst dla prostych zadań const oversizedContexts = usage.filter(u => u.task.complexity < 3 && u.inputTokens > 10000 );
// Powtarzające się podobne żądania const duplicates = this.findDuplicateRequests(usage);
// Nieefektywny wybór modelu const suboptimalModels = usage.filter(u => this.isSuboptimalModel(u) );
return [...oversizedContexts, ...duplicates, ...suboptimalModels]; }}
// Buforuj odpowiedzi AI, aby zmniejszyć użycie tokenówclass ResponseCache { private cache = new LRUCache<string, CachedResponse>({ max: 1000, ttl: 1000 * 60 * 60 * 24, // 24 godziny updateAgeOnGet: true });
async getCachedOrGenerate( prompt: string, generator: () => Promise<Response> ): Promise<Response> { const key = this.generateCacheKey(prompt); const cached = this.cache.get(key);
if (cached && this.isValid(cached)) { // Sprawdź, czy kontekst znacząco się zmienił if (await this.contextStillValid(cached)) { this.recordCacheHit(key); return cached.response; } }
// Wygeneruj nową odpowiedź const response = await generator();
// Buforuj jeśli stosowne if (this.shouldCache(prompt, response)) { this.cache.set(key, { response, prompt, timestamp: Date.now(), contextHash: await this.hashContext() }); }
return response; }}
// Używaj ponownie części poprzednich odpowiedziclass PartialResponseCache { async findReusableSegments(newPrompt: string): Promise<ReusableSegment[]> { const segments = []; const similar = await this.findSimilarPrompts(newPrompt);
for (const cached of similar) { const reusable = this.extractReusableSegments( cached.prompt, cached.response, newPrompt );
segments.push(...reusable); }
return this.rankByRelevance(segments, newPrompt); }
buildPromptWithCache( basePrompt: string, segments: ReusableSegment[] ): EnhancedPrompt { return { prompt: basePrompt, context: segments.map(s => ({ type: 'cached_response', content: s.content, relevance: s.relevance })), expectedSavings: this.estimateTokenSavings(segments) }; }}
// Zarządzaj budżetami tokenów w zespołach i projektachclass TokenBudgetManager { private budgets = new Map<string, Budget>();
async allocateBudget(period: Period): Promise<AllocationPlan> { const totalBudget = this.getTotalBudget(period); const teams = await this.getTeams(); const historicalUsage = await this.getHistoricalUsage();
// Inteligentna alokacja na podstawie wielu czynników const allocations = teams.map(team => ({ team, allocation: this.calculateAllocation(team, { historicalUsage: historicalUsage[team.id], teamSize: team.size, projectPriority: team.priority, efficiency: this.calculateEfficiency(team) }) }));
return { period, totalBudget, allocations, rules: this.generateBudgetRules(allocations) }; }
enforcebudget(request: TokenRequest): Promise<boolean> { const budget = this.budgets.get(request.teamId);
if (!budget) return false;
const projected = budget.used + request.estimatedTokens;
if (projected > budget.limit) { // Sprawdź, czy żądanie kwalifikuje się do wyjątku if (this.qualifiesForException(request)) { return this.requestBudgetException(request); }
return false; }
return true; }}
Szablony promptów
Ponownie używaj zoptymalizowanych promptów dla typowych zadań
Przetwarzanie wsadowe
Łącz wiele małych żądań w jedno
Progresywne ulepszanie
Zacznij prosto, dodawaj kontekst tylko w razie potrzeby
Użycie poza szczytem
Planuj nieurgentne zadania na niższe stawki
// Inteligentnie przycinaj kontekst na podstawie istotnościclass DynamicContextPruner { async pruneContext( context: Context, targetTokens: number ): Promise<PrunedContext> { // Oceń każdy element kontekstu const scored = await Promise.all( context.items.map(async item => ({ item, score: await this.scoreRelevance(item, context.task) })) );
// Sortuj według istotności scored.sort((a, b) => b.score - a.score);
// Wybierz elementy w ramach budżetu tokenów const selected = []; let tokenCount = 0;
for (const { item, score } of scored) { const itemTokens = await this.countTokens(item);
if (tokenCount + itemTokens <= targetTokens) { selected.push(item); tokenCount += itemTokens; } else if (score > 0.8) { // Spróbuj skompresować wartościowe elementy const compressed = await this.compress(item); if (tokenCount + compressed.tokens <= targetTokens) { selected.push(compressed.item); tokenCount += compressed.tokens; } } }
return { items: selected, totalTokens: tokenCount, prunedItems: scored.length - selected.length }; }}
// Ponownie używaj tokenów z poprzednich interakcjiclass TokenRecycler { async recycleTokens(conversation: Conversation): Promise<RecycledContext> { const messages = conversation.messages; const recycled = [];
// Zidentyfikuj segmenty nadające się do ponownego użycia for (let i = 0; i < messages.length - 1; i++) { const message = messages[i];
if (this.isReusable(message)) { recycled.push({ content: this.extractReusableContent(message), summary: await this.summarize(message), tokens: await this.countTokens(message) }); } }
// Zbuduj skompresowany kontekst return { summary: this.buildSummary(recycled), keyPoints: this.extractKeyPoints(recycled), savedTokens: this.calculateSavings(messages, recycled) }; }}
// Zarządzaj współdzielonymi pulami tokenów dla zespołówclass SharedTokenPool { async drawFromPool(request: PoolRequest): Promise<TokenAllocation> { const pool = await this.getPool(request.teamId);
// Sprawdź dostępność if (pool.available < request.tokens) { // Spróbuj pożyczyć z innych pul const borrowed = await this.borrowTokens( request.teamId, request.tokens - pool.available );
if (!borrowed.success) { throw new InsufficientTokensError(); } }
// Przydziel tokeny const allocation = { tokens: request.tokens, user: request.userId, purpose: request.purpose, timestamp: new Date(), expiresAt: this.calculateExpiry(request) };
await this.recordAllocation(allocation);
return allocation; }}
Monitoruj ciągle
Optymalizuj proaktywnie
Edukuj zespół
Iteruj i ulepszaj
Metryka | Formuła | Cel |
---|---|---|
Efektywność tokenów | Wartość wyjścia / Użyte tokeny | >0.8 |
Wskaźnik trafień cache | Buforowane odpowiedzi / Wszystkie żądania | >30% |
Koszt na funkcję | Koszt tokenów / Dostarczone funkcje | Malejący |
Efektywność kontekstu | Istotne tokeny / Całkowity kontekst | >70% |
Pamiętaj: skuteczne zarządzanie tokenami nie polega na używaniu mniejszej liczby tokenów—polega na wydobyciu maksymalnej wartości z każdego używanego tokenu. Skup się na ROI, nie tylko na redukcji kosztów.