namespace AxCopilot.Services; /// /// 모델별 토큰 추정 및 비용 계산 서비스. /// tiktoken 없이 문자 기반 가중치 추정을 사용합니다. /// public static class TokenEstimator { /// 텍스트의 토큰 수를 추정합니다 (CJK/영문 가중치 적용). public static int Estimate(string text) { if (string.IsNullOrEmpty(text)) return 0; int cjkChars = 0; int totalChars = text.Length; foreach (var c in text) { if (c >= 0xAC00 && c <= 0xD7A3) cjkChars++; // 한글 else if (c >= 0x3000 && c <= 0x9FFF) cjkChars++; // CJK } double ratio = totalChars > 0 ? (double)cjkChars / totalChars : 0; // 영문: ~4글자/토큰, 한글: ~2글자/토큰 double charsPerToken = 4.0 - ratio * 2.0; return Math.Max(1, (int)(totalChars / charsPerToken)); } /// 메시지 리스트의 총 토큰 수를 추정합니다. public static int EstimateMessages(IEnumerable messages) { int total = 0; foreach (var m in messages) total += Estimate(m.Content) + 4; // 메시지 오버헤드 return total; } /// 비용을 추정합니다 (USD 기준). public static (double InputCost, double OutputCost) EstimateCost( int promptTokens, int completionTokens, string service, string model) { var (inputPrice, outputPrice) = GetPricing(service, model); return (promptTokens / 1_000_000.0 * inputPrice, completionTokens / 1_000_000.0 * outputPrice); } /// 모델별 1M 토큰 가격 (USD). (inputPrice, outputPrice) public static (double Input, double Output) GetPricing(string service, string model) { var key = $"{service}:{model}".ToLowerInvariant(); // 2026년 기준 가격표 return key switch { _ when key.Contains(string.Concat("cl", "aude-opus")) => (15.0, 75.0), _ when key.Contains(string.Concat("cl", "aude-sonnet")) => (3.0, 15.0), _ when key.Contains(string.Concat("cl", "aude-haiku")) => (0.25, 1.25), _ when key.Contains("gemini-2.5-pro") => (1.25, 10.0), _ when key.Contains("gemini-2.5-flash") => (0.15, 0.6), _ when key.Contains("gemini-2.0") => (0.1, 0.4), _ when key.Contains("ollama") => (0, 0), // 로컬 _ when key.Contains("vllm") => (0, 0), // 로컬 _ => (1.0, 3.0), // 기본 추정 }; } /// 토큰 수를 읽기 쉬운 문자열로 포맷합니다. public static string Format(int count) => count switch { >= 1_000_000 => $"{count / 1_000_000.0:0.#}M", >= 1_000 => $"{count / 1_000.0:0.#}K", _ => count.ToString(), }; /// 비용을 읽기 쉬운 문자열로 포맷합니다. public static string FormatCost(double usd) => usd switch { 0 => "무료", < 0.01 => $"${usd:F4}", < 1.0 => $"${usd:F3}", _ => $"${usd:F2}", }; /// 컨텍스트 사용률을 계산합니다 (0.0~1.0). public static double GetContextUsage(int currentTokens, int maxContextTokens) { if (maxContextTokens <= 0) return 0; return Math.Min(1.0, (double)currentTokens / maxContextTokens); } }