관리자 사용량 근거를 정교화

This commit is contained in:
Yun Chan 2026-08-29 23:59:17 +09:00
parent ccdcfcd2f5
commit 707dba4f8f
10 changed files with 1136 additions and 209 deletions

View file

@ -13,6 +13,7 @@ import shutil
import tempfile
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, AsyncIterator, Iterable, Literal, cast
@ -80,6 +81,10 @@ def _now() -> float:
return time.time()
def _utcnow() -> datetime:
return datetime.now(timezone.utc)
def _efforts(values: Iterable[str]) -> list[ReasoningEffort]:
allowed = set(ENGINE_REASONING_EFFORTS)
return [cast(ReasoningEffort, value) for value in values if value in allowed]
@ -597,6 +602,7 @@ def _structured_or_none(text: str, req: GenerateRequest) -> dict[str, Any] | Non
async def _generate_codex(
req: GenerateRequest, system_prompt: str, user_payload: str
) -> ProviderGenerateResult:
pricing_started_at = _utcnow()
binary = _binary("CODEX_BIN", "codex")
if binary is None:
raise ProviderError("Codex CLI를 찾을 수 없습니다.")
@ -652,6 +658,7 @@ async def _generate_codex(
model=model,
tokens_in=tokens_in,
tokens_out=tokens_out,
priced_at=pricing_started_at,
cached_input_tokens=cached_input_tokens,
)
return ProviderGenerateResult(
@ -685,6 +692,7 @@ async def _stream_agy(
--continue/--conversation을 쓰지 않는다. 회기 메모리는 요청의 마스킹된 prompt가
소유하고, 프로세스는 응답 종료한다.
"""
pricing_started_at = _utcnow()
binary = _binary("AGY_BIN", "agy")
if binary is None:
raise ProviderError("Agy CLI를 찾을 수 없습니다.")
@ -781,6 +789,7 @@ async def _stream_agy(
model=model,
tokens_in=tokens_in,
tokens_out=tokens_out,
priced_at=pricing_started_at,
cached_input_tokens=cached_input_tokens,
)
yield ProviderStreamEvent(
@ -800,6 +809,7 @@ async def _stream_agy(
async def _generate_claude_api(
req: GenerateRequest, system_prompt: str
) -> ProviderGenerateResult:
pricing_started_at = _utcnow()
api_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
if not api_key:
raise ProviderError("ANTHROPIC_API_KEY가 설정되지 않았습니다.")
@ -849,6 +859,7 @@ async def _generate_claude_api(
model=str(body.get("model") or model),
tokens_in=tokens_in,
tokens_out=tokens_out,
priced_at=pricing_started_at,
cached_input_tokens=int(usage.get("cache_read_input_tokens") or 0),
)
return ProviderGenerateResult(

View file

@ -1,5 +1,6 @@
import json
import unittest
from datetime import datetime, timezone
from unittest.mock import AsyncMock, patch
from app.contracts.engine_gateway import EngineMessage, GenerateRequest
@ -274,19 +275,19 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
source="live_cli",
models=[
provider_registry.EngineModelOption(
id="gemini-3.6-flash-high",
label="Gemini 3.6 Flash (High)",
id="gemini-3.7-flash-high",
label="Gemini 3.7 Flash (High)",
reasoning_efforts=["high"],
default_reasoning_effort="high",
)
],
default_model="gemini-3.6-flash-high",
default_model="gemini-3.7-flash-high",
default_reasoning_effort="high",
fetched_at=1,
)
request = GenerateRequest(
provider="agy_cli",
model="gemini-3.6-flash-high",
model="gemini-3.7-flash-high",
reasoning_effort="high",
messages=[EngineMessage(role="user", content="hello")],
)
@ -322,6 +323,11 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
with (
patch.object(provider_registry, "_binary", return_value="agy"),
patch.object(
provider_registry,
"_utcnow",
return_value=datetime(2026, 8, 28, tzinfo=timezone.utc),
),
patch.object(
provider_registry,
"discover_capabilities",
@ -342,7 +348,7 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(result.text, "OK")
self.assertEqual(result.tokens_in, 12)
self.assertEqual(result.tokens_out, 2)
self.assertEqual(result.cost_usd, 0.0000303)
self.assertEqual(result.cost_usd, 0.00001515)
args = captured[0]
print_index = args.index("--print")
self.assertEqual(print_index, len(args) - 2)
@ -413,6 +419,11 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
with (
patch.object(provider_registry, "_binary", return_value="agy.exe"),
patch.object(
provider_registry,
"_utcnow",
return_value=datetime(2026, 8, 28, tzinfo=timezone.utc),
),
patch.object(
provider_registry,
"discover_capabilities",
@ -438,7 +449,7 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(events[-1].result.text, "안녕")
self.assertEqual(events[-1].result.tokens_in, 12)
self.assertEqual(events[-1].result.tokens_out, 2)
self.assertEqual(events[-1].result.cost_usd, 0.0000303)
self.assertEqual(events[-1].result.cost_usd, 0.00001515)
args = captured[0]
self.assertIn("--output-format", args)
self.assertEqual(args[args.index("--output-format") + 1], "stream-json")