55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
"""LLM 호출 계량과 감사 훅의 공통 실행 경로."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from collections.abc import Awaitable, Callable
|
|
from typing import Any
|
|
|
|
from ..engine_client import EngineClient, GenerateRequest, GenerateResponse
|
|
|
|
LlmAuditHook = Callable[[dict[str, Any]], Awaitable[None]]
|
|
|
|
|
|
async def record_llm_audit(
|
|
audit_hook: LlmAuditHook | None,
|
|
**payload: Any,
|
|
) -> None:
|
|
"""감사 저장소 장애가 사용자 응답을 막지 않도록 훅 실패를 격리한다."""
|
|
if audit_hook is None:
|
|
return
|
|
try:
|
|
await audit_hook(payload)
|
|
except Exception:
|
|
return
|
|
|
|
|
|
async def generate_with_audit(
|
|
engine: EngineClient,
|
|
request: GenerateRequest,
|
|
audit_hook: LlmAuditHook | None,
|
|
*,
|
|
timeout: float | None = None,
|
|
) -> GenerateResponse:
|
|
"""비스트리밍 LLM 호출의 지연·토큰·비용 기록을 한 계약으로 고정한다."""
|
|
started = time.perf_counter()
|
|
# deep-loop처럼 호출별 deadline이 필요한 경로가 audit 경계를 지나도 사라지지
|
|
# 않게 한다. 기본(None)은 기존 엔진 client deadline을 그대로 쓴다.
|
|
response = (
|
|
await engine.generate(request, timeout=timeout)
|
|
if timeout is not None
|
|
else await engine.generate(request)
|
|
)
|
|
latency_ms = int((time.perf_counter() - started) * 1000)
|
|
await record_llm_audit(
|
|
audit_hook,
|
|
session_id=request.session_id,
|
|
provider=response.provider,
|
|
model=response.model,
|
|
tokens_in=response.tokens_in,
|
|
tokens_out=response.tokens_out,
|
|
cost_usd=response.cost_usd,
|
|
inference_geo=response.inference_geo,
|
|
latency_ms=latency_ms,
|
|
)
|
|
return response
|