본문 바로가기
C.W.K.
Stream
Lesson 05 of 06 · published

LLM 호출은 비용·지연·품질을 함께 관측해

~14 min · observability, tracing, metrics

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

호출마다 남길 차원을 정해

모델 ID, 기능 표지, 입력·출력·캐시 토큰, stop_reason, 오류 종류, 사용한 도구 이름을 기록해. 스트리밍이면 첫 토큰까지의 시간(TTFT)과 마지막 토큰까지의 시간(TTLT)을 나눠 봐. 비용 급증이나 품질 하락을 잘라 볼 축이 돼.

도구 루프 전체를 한 추적으로 묶어

에이전트 한 작업이 5~10번 API를 왕복할 수 있어. 각 호출을 서로 무관한 기록으로 남기면 사용자가 느낀 한 작업을 재구성하기 어렵지. OpenTelemetry의 부모 추적 아래 각 라운드를 자식 span으로 두면 전체 지연과 병목을 함께 볼 수 있어.

일부 원문은 가리고 보존해

특정 프롬프트와 입력 모양이 비용 급증이나 회귀를 만드는 경우가 많아. 개인정보를 가린 전체 프롬프트·응답 쌍을 일정 비율로 오래 보관하면 나중에 다시 실행해 원인을 찾을 수 있어. cwkPippa의 JSONL은 모든 사건을 내구성 있게 남겨 회귀를 재생하는 기반이 돼.

원칙: 어제 어느 기능이 비용을 가장 많이 썼고 왜 그랬는지 답할 수 있어야 관측성이 있는 거야.

Code

Per-call structured 로그·python
import time, json

def call_with_telemetry(messages, *, feature: str, log_path: str):
    t0 = time.perf_counter()
    try:
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            messages=messages,
        )
        latency_ms = int((time.perf_counter() - t0) * 1000)
        with open(log_path, "a") as f:
            f.write(json.dumps({
                "feature": feature,
                "model": "claude-sonnet-4-6",
                "latency_ms": latency_ms,
                "input": resp.usage.input_tokens,
                "output": resp.usage.output_tokens,
                "cache_read": resp.usage.cache_read_input_tokens,
                "stop_reason": resp.stop_reason,
            }) + "\n")
        return resp
    except Exception as e:
        with open(log_path, "a") as f:
            f.write(json.dumps({"feature": feature, "error": type(e).__name__, "msg": str(e)}) + "\n")
        raise
Tool 루프 주변 OpenTelemetry trace·python
from opentelemetry import trace
tracer = trace.get_tracer("claude")

async def traced_loop(prompt: str):
    with tracer.start_as_current_span("claude.tool_loop") as parent:
        parent.set_attribute("prompt.length", len(prompt))
        for round_i in range(MAX_ITERS):
            with tracer.start_as_current_span(f"claude.round.{round_i}") as span:
                resp = await client.messages.create(...)
                span.set_attribute("stop_reason", resp.stop_reason)
                span.set_attribute("output_tokens", resp.usage.output_tokens)
                if resp.stop_reason != "tool_use":
                    return resp

External links

Exercise

중요 Claude 경로 하나에 지연·토큰·stop_reason·기능 표지를 기록해. “어제 어느 기능이 가장 많이 썼나”에 답하는 대시보드 질의도 만들어.
Hint
키와 값으로 나뉜 구조화 기록이 아니면 이런 질의를 싸게 만들 수 없어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.