관리자 사용량 근거를 정교화
This commit is contained in:
parent
ccdcfcd2f5
commit
707dba4f8f
10 changed files with 1136 additions and 209 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue