feat: [additional] RAG 검색에 토큰 비용 안내 및 사용량 표시 추가
- 검색 전 비용 안내 문구 (건당 약 3~10원, AI 토큰 사용량 기록 안내) - 검색 결과에 토큰 사용량 바 표시 (입력/출력/합계/비용) - AiTokenHelper + AiPricingConfig 연동으로 정확한 비용 계산
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
use App\Helpers\AiTokenHelper;
|
||||
use App\Models\System\AiConfig;
|
||||
use App\Models\System\AiPricingConfig;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -282,7 +283,21 @@ private function callGemini(AiConfig $config, string $query, string $context, ar
|
||||
$result = $response->json();
|
||||
$answer = $result['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
||||
|
||||
// 토큰 사용량 저장
|
||||
// 토큰 사용량 계산
|
||||
$usage = $result['usageMetadata'] ?? [];
|
||||
$promptTokens = $usage['promptTokenCount'] ?? 0;
|
||||
$completionTokens = $usage['candidatesTokenCount'] ?? 0;
|
||||
$totalTokens = $usage['totalTokenCount'] ?? 0;
|
||||
|
||||
// 비용 계산
|
||||
$pricing = AiPricingConfig::getActivePricing('gemini');
|
||||
$inputPrice = $pricing ? (float) $pricing->input_price_per_million / 1_000_000 : 0.10 / 1_000_000;
|
||||
$outputPrice = $pricing ? (float) $pricing->output_price_per_million / 1_000_000 : 0.40 / 1_000_000;
|
||||
$costUsd = ($promptTokens * $inputPrice) + ($completionTokens * $outputPrice);
|
||||
$exchangeRate = AiPricingConfig::getExchangeRate();
|
||||
$costKrw = $costUsd * $exchangeRate;
|
||||
|
||||
// 토큰 사용량 DB 저장
|
||||
AiTokenHelper::saveGeminiUsage($result, $result['modelVersion'] ?? $config->model, 'RAG검색');
|
||||
|
||||
// 참조 문서 목록 추출
|
||||
@@ -297,6 +312,13 @@ private function callGemini(AiConfig $config, string $query, string $context, ar
|
||||
'references' => $references,
|
||||
'searched_count' => count($documents),
|
||||
'model' => $config->model,
|
||||
'token_usage' => [
|
||||
'prompt_tokens' => $promptTokens,
|
||||
'completion_tokens' => $completionTokens,
|
||||
'total_tokens' => $totalTokens,
|
||||
'cost_usd' => round($costUsd, 6),
|
||||
'cost_krw' => round($costKrw, 2),
|
||||
],
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -65,6 +65,17 @@
|
||||
.rag-answer-body blockquote { border-left: 3px solid #3b82f6; padding-left: 14px; margin: 12px 0; color: #64748b; }
|
||||
.rag-answer-body strong { font-weight: 600; color: #1e293b; }
|
||||
|
||||
/* 토큰 비용 바 */
|
||||
.rag-token-bar { display: flex; align-items: center; gap: 14px; background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 10px 16px; margin-bottom: 16px; flex-wrap: wrap; }
|
||||
.rag-token-item { display: flex; align-items: center; gap: 5px; font-size: 0.75rem; color: #92400e; }
|
||||
.rag-token-item .label { color: #a16207; }
|
||||
.rag-token-item .value { font-weight: 600; }
|
||||
.rag-token-sep { width: 1px; height: 14px; background: #fde68a; }
|
||||
|
||||
/* 비용 안내 */
|
||||
.rag-cost-notice { background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 10px 16px; margin-bottom: 24px; display: flex; align-items: flex-start; gap: 8px; font-size: 0.78rem; color: #0369a1; line-height: 1.5; }
|
||||
.rag-cost-notice svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
|
||||
|
||||
/* 참조 문서 */
|
||||
.rag-refs { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 16px 20px; }
|
||||
.rag-refs-title { font-size: 0.78rem; font-weight: 600; color: #64748b; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
|
||||
@@ -127,6 +138,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 비용 안내 --}}
|
||||
<div class="rag-cost-notice">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<span>검색 1회당 Gemini API 토큰이 소모됩니다. 관련 문서(최대 100KB)를 컨텍스트로 전달하므로 약 <strong>25,000~50,000 토큰</strong>이 사용되며, 예상 비용은 <strong>건당 약 3~10원</strong>입니다. 사용 내역은 시스템설정 > AI 토큰 사용량에 기록됩니다.</span>
|
||||
</div>
|
||||
|
||||
{{-- 검색 --}}
|
||||
<div class="rag-search">
|
||||
<input type="text" class="rag-search-input" id="ragQuery" placeholder="SAM 프로젝트에 대해 질문하세요... (예: API 개발 규칙, 데이터베이스 구조)" autocomplete="off">
|
||||
@@ -227,7 +244,37 @@ function setQuery(text) {
|
||||
}
|
||||
|
||||
function renderAnswer(data) {
|
||||
let html = `
|
||||
let html = '';
|
||||
|
||||
// 토큰 사용량 바
|
||||
if (data.token_usage) {
|
||||
const t = data.token_usage;
|
||||
html += `
|
||||
<div class="rag-token-bar">
|
||||
<div class="rag-token-item">
|
||||
<span class="label">입력</span>
|
||||
<span class="value">${Number(t.prompt_tokens).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="rag-token-sep"></div>
|
||||
<div class="rag-token-item">
|
||||
<span class="label">출력</span>
|
||||
<span class="value">${Number(t.completion_tokens).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="rag-token-sep"></div>
|
||||
<div class="rag-token-item">
|
||||
<span class="label">합계</span>
|
||||
<span class="value">${Number(t.total_tokens).toLocaleString()} tokens</span>
|
||||
</div>
|
||||
<div class="rag-token-sep"></div>
|
||||
<div class="rag-token-item">
|
||||
<span class="label">비용</span>
|
||||
<span class="value">$${t.cost_usd.toFixed(4)} (${Math.round(t.cost_krw).toLocaleString()}원)</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="rag-answer">
|
||||
<div class="rag-answer-header">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /></svg>
|
||||
|
||||
Reference in New Issue
Block a user