티스토리 뷰

반응형

Python으로 공부하는 OpenAI 17편 — 운영은 “잘 되는지”가 아니라 “지금 이상한지 빨리 아는지”로 갈립니다

여기까지 오면 기능은 꽤 많이 붙었습니다.
채팅도 되고, RAG도 되고, 백그라운드 작업도 되고, 재시도와 멱등성도 어느 정도 잡았죠.

근데 운영은 여기서 또 결이 달라집니다.
실제로 서비스가 흔들릴 때 제일 무서운 건 에러 하나 자체가 아니라, 이상해지고 있는데 아무도 모르고 지나가는 시간이에요.
사용자는 “오늘 좀 느리네?” 하고 나가고, 운영자는 “아까부터 토큰이 왜 늘지?” 싶고, 개발자는 “RAG가 흔들린 건가, 모델이 느린 건가, Redis가 밀린 건가”를 뒤늦게 추적합니다.

그래서 이번 편은 관측성, 그러니까 observability 이야기입니다.
거창하게 들리지만, 저는 그냥 이렇게 이해하는 편이 좋다고 봐요.

지금 우리 AI 기능이 어디서, 왜, 얼마나 이상한지 빨리 알아차릴 수 있는 구조

OpenAI 쪽도 운영 단계 가이드에서 production best practices, latency optimization, cost optimization, usage/cost 추적을 각각 따로 다루고 있고, Agents SDK 쪽은 tracing과 observability를 별도 가이드로 뽑아둘 정도로 “만든 뒤 관찰하는 일”을 중요하게 보고 있습니다. Agents SDK는 기본 서버 경로에서 tracing이 기본 활성화되고, 각 run이 model call, tool call, handoff, guardrail, custom span 같은 구조화된 기록을 남길 수 있다고 설명합니다. (OpenAI 개발자)


1. 모니터링보다 한 단계 더 필요한 게 observability입니다

처음엔 보통 모니터링부터 붙입니다.

  • 서버 살아 있나
  • 500 에러 났나
  • CPU 올랐나
  • 응답시간 길어졌나

이것도 필요해요.
근데 AI 기능은 여기서 한 단계 더 들어가야 합니다.

왜냐하면 AI 백엔드는 “서버는 정상인데 결과가 이상한” 일이 자주 생기거든요.

예를 들어 이런 상황이 있습니다.

  • API는 200 OK인데 답변 품질이 갑자기 흔들림
  • RAG 검색은 되는데 relevance가 떨어짐
  • 모델은 응답했는데 출력이 지나치게 길어짐
  • prompt caching이 안 먹어서 비용만 급증함
  • Agents 워크플로는 끝났는데 tool call 한 단계에서 병목이 생김

이런 건 CPU나 HTTP status만 봐서는 잘 안 보입니다.
그래서 observability에서는 보통 logs, metrics, traces 세 축으로 보는 게 자연스럽습니다. OpenAI Agents SDK observability 가이드도 trace 안에 전체 run, 각 model call, tool calls와 outputs, handoffs, guardrails, custom spans가 들어간다고 설명하고, trace grading 가이드는 Logs > Traces에서 워크플로를 보고 grader까지 연결할 수 있다고 안내합니다. (OpenAI 개발자)


2. AI 백엔드에서 꼭 봐야 하는 세 가지: 로그, 메트릭, 트레이스

저는 이 셋을 이렇게 구분합니다.

로그(log)

“무슨 일이 있었는지”를 남깁니다.
예: 어떤 세션이 어떤 모델을 썼고, 몇 개 chunk를 붙였고, 어떤 오류가 났는지

메트릭(metric)

“지금 상태가 어떤지”를 숫자로 봅니다.
예: p95 latency, error rate, average output tokens, cache hit 관련 비율

트레이스(trace)

“한 요청이 어디를 어떻게 지나갔는지”를 연결해서 봅니다.
예: API → retrieval → rerank → responses.create → post-processing 순서에서 어디가 병목인지

OpenAI는 usage/cost를 조직 단위 API와 cookbook으로 모니터링하는 흐름을 제공하고, Agents SDK는 trace를 통해 워크플로 단위의 model/tool 단계 기록을 보여줍니다. 즉, 숫자만 보는 것과 요청 흐름을 보는 것을 분리해서 가져가는 게 공식 문서 방향과도 잘 맞습니다. (OpenAI 개발자)


3. 제일 먼저 남겨야 하는 로그는 생각보다 단순합니다

처음부터 거대한 관측 플랫폼을 붙일 필요는 없습니다.
사실 초반엔 아래만 잘 남겨도 꽤 좋아집니다.

  • request_id
  • session_id
  • endpoint
  • model
  • latency_ms
  • input_tokens
  • cached_input_tokens
  • output_tokens
  • total_tokens
  • retrieved_chunk_count
  • history_turn_count
  • status
  • error_type

OpenAI usage/cost cookbook은 usage와 cost를 시간별, 모델별로 집계해서 시각화하는 예제를 보여주고, usage 계열 API 레퍼런스는 input_tokens, output_tokens, num_model_requests 같은 집계 필드를 제공합니다. Prompt caching 가이드는 cached token 개념을 공식적으로 설명하므로, cached input도 로그 항목으로 빼두는 게 운영에 유리합니다. (OpenAI 개발자)

제가 느끼기엔 이 로그가 있고 없고의 차이가 꽤 큽니다.
없으면 “요즘 좀 느린데?” 수준이고,
있으면 “gpt-5.4-mini를 쓰는 /rag-chat 엔드포인트에서 retrieved chunks가 6개로 늘어난 뒤 input tokens가 튀었네”까지 보이거든요.


4. 반대로, 절대 함부로 남기면 안 되는 것도 있습니다

이건 진짜 중요합니다.
AI 서비스 로그는 잘못 남기면 디버깅보다 유출 위험이 더 커집니다.

운영에서는 최소한 아래는 조심하는 게 좋습니다.

  • API key
  • Authorization header
  • webhook signature header
  • 사용자 원문 프롬프트 전체
  • 업로드 문서 원문 전체
  • 검색된 chunk 전체 덤프
  • raw response 전체 출력

OpenAI production best practices는 API 키를 코드나 공개 저장소에 두지 말고 환경변수나 secret manager에 두라고 강조합니다. 이 원칙은 로그에도 똑같이 적용된다고 보는 게 맞아요. 키나 민감 문서를 남기지 않는 게 먼저입니다. (OpenAI 개발자)

즉, 운영 로그는 “문제를 찾기에 충분할 만큼만” 남겨야 합니다.
많이 남기는 것보다, 안전하게 남기는 것이 더 중요합니다.


5. 메트릭은 “보여주기용 숫자”가 아니라 알림 기준이 돼야 합니다

대시보드만 예쁘다고 운영이 잘 되는 건 아니에요.
실제로 중요한 건 “언제 알림이 울려야 하냐”입니다.

AI 백엔드에서 저는 보통 이 메트릭들을 먼저 봅니다.

  • 요청 수(RPS 또는 분당 요청 수)
  • 에러율
  • p50 / p95 / p99 latency
  • input / output / total tokens 평균
  • cached input tokens 비율
  • 모델별 요청 분포
  • endpoint별 비용
  • RAG hit 여부 / 검색 chunk 수
  • background job queue depth
  • background job fail rate

Latency optimization 가이드는 지연을 줄이는 핵심 원칙을 따로 다루고 있고, production best practices는 latency, scaling, cost 관리를 운영 핵심으로 설명합니다. Usage/Costs cookbook은 모델별 usage 분포와 시간대별 usage를 시각화하는 예제를 제공합니다. 결국 운영에서는 이런 수치가 단순 리포트가 아니라 즉시 대응 신호가 되어야 합니다. (OpenAI 개발자)


6. 알림은 너무 많이 울려도 실패고, 너무 늦게 울려도 실패입니다

이건 정말 운영해보면 알게 됩니다.
처음엔 뭐든 다 알림 걸고 싶어요.
근데 그러면 하루 종일 시끄럽고, 결국 아무도 안 봅니다.

저는 보통 이런 식으로 나눠서 생각합니다.

즉시 알림이 필요한 것

  • 에러율 급증
  • production OpenAI 인증 실패
  • quota 또는 billing 관련 오류 급증
  • background job fail rate 급증
  • webhook 검증 실패 급증

경향성으로 봐야 하는 것

  • 평균 토큰 수 증가
  • cache 비율 하락
  • RAG chunk 수 증가
  • 응답 길이 증가
  • 비용 추세 상승

Error codes 가이드는 authentication, rate limit, quota exceeded, unsupported region 같은 오류 유형을 구분해서 설명합니다. Usage/cost 자료와 pricing 문서를 같이 보면 비용 증가도 갑자기 터지는 장애보다 “추세 이상”으로 볼 때 더 잘 잡힙니다. (OpenAI 개발자)

그러니까 알림은 두 층으로 보는 게 좋아요.

  • 장애 알림
  • 품질/비용 이상 징후 알림

7. FastAPI에서 최소 구조화 로그부터 붙여봅시다

아래 코드는 아주 단순하지만, 실전에서 꽤 쓸 만한 시작점입니다.

app/core/logging.py

import json
import logging
from datetime import datetime, timezone
from typing import Any


class JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        payload: dict[str, Any] = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }

        if hasattr(record, "extra_data") and isinstance(record.extra_data, dict):
            payload.update(record.extra_data)

        if record.exc_info:
            payload["exception"] = self.formatException(record.exc_info)

        return json.dumps(payload, ensure_ascii=False)


def configure_logging(level: str = "INFO") -> None:
    handler = logging.StreamHandler()
    handler.setFormatter(JsonFormatter())

    root = logging.getLogger()
    root.setLevel(level)
    root.handlers.clear()
    root.addHandler(handler)

이렇게 JSON 로그로 가면 나중에 로그 수집기나 검색 도구에 넣기가 훨씬 편합니다.
AI 백엔드는 request 단위 정보를 많이 같이 봐야 해서, 자유 텍스트 로그보다 구조화 로그가 확실히 낫습니다.


8. 요청 단위 메트릭 후보를 함께 남기는 예제

app/services/observability_service.py

import logging
from time import perf_counter
from typing import Any

logger = logging.getLogger("app.observability")


class RequestObservation:
    def __init__(
        self,
        endpoint: str,
        model: str,
        session_id: str | None,
        rag_enabled: bool,
    ) -> None:
        self.endpoint = endpoint
        self.model = model
        self.session_id = session_id
        self.rag_enabled = rag_enabled
        self.started = perf_counter()

    def success(
        self,
        input_tokens: int,
        output_tokens: int,
        cached_input_tokens: int,
        retrieved_chunk_count: int,
        history_turn_count: int,
    ) -> None:
        latency_ms = int((perf_counter() - self.started) * 1000)

        logger.info(
            "openai_request_succeeded",
            extra={
                "extra_data": {
                    "endpoint": self.endpoint,
                    "model": self.model,
                    "session_id": self.session_id,
                    "rag_enabled": self.rag_enabled,
                    "latency_ms": latency_ms,
                    "input_tokens": input_tokens,
                    "output_tokens": output_tokens,
                    "cached_input_tokens": cached_input_tokens,
                    "total_tokens": input_tokens + output_tokens,
                    "retrieved_chunk_count": retrieved_chunk_count,
                    "history_turn_count": history_turn_count,
                    "status": "succeeded",
                }
            },
        )

    def failure(self, error_type: str, message: str) -> None:
        latency_ms = int((perf_counter() - self.started) * 1000)

        logger.error(
            "openai_request_failed",
            extra={
                "extra_data": {
                    "endpoint": self.endpoint,
                    "model": self.model,
                    "session_id": self.session_id,
                    "rag_enabled": self.rag_enabled,
                    "latency_ms": latency_ms,
                    "error_type": error_type,
                    "message_safe": message[:300],
                    "status": "failed",
                }
            },
        )

이 구조가 좋은 이유는 하나예요.
로그 메시지가 아니라 운영에서 보고 싶은 필드를 먼저 정하게 만들어줍니다.


9. FastAPI 엔드포인트에서 관측 포인트를 넣는 흐름

반응형

app/api/chat_router.py

from fastapi import APIRouter, HTTPException

from app.services.observability_service import RequestObservation

router = APIRouter(prefix="/chat", tags=["chat"])


@router.post("")
async def chat(request: dict):
    observation = RequestObservation(
        endpoint="/chat",
        model="gpt-5.4-mini",
        session_id=request.get("session_id"),
        rag_enabled=False,
    )

    try:
        # 실제로는 여기서 OpenAI 호출
        output_text = "예시 응답"
        input_tokens = 120
        output_tokens = 80
        cached_input_tokens = 40

        observation.success(
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            cached_input_tokens=cached_input_tokens,
            retrieved_chunk_count=0,
            history_turn_count=3,
        )

        return {"answer": output_text}

    except Exception as e:
        observation.failure(error_type=type(e).__name__, message=str(e))
        raise HTTPException(status_code=500, detail="Internal Server Error") from e

여기서 중요한 건
“에러가 나면 로그 남긴다”가 아니라
성공했을 때도 운영 메트릭 후보를 남긴다는 점입니다.

실제로는 성공 로그가 더 중요할 때가 많아요.
비용이 튀고, 토큰이 불어나고, latency가 늘어나는 건 대부분 성공 요청 안에서 먼저 보이니까요.


10. usage와 cost는 애플리케이션 로그만 보지 말고 조직 집계도 같이 봐야 합니다

이건 꽤 중요합니다.

애플리케이션 로그는 request 단위로 좋고,
OpenAI Usage/Costs API는 조직/프로젝트 단위로 좋습니다.

OpenAI cookbook은 Usage API와 Costs API로 usage와 비용을 가져와 pandas와 matplotlib로 시각화하는 예제를 제공합니다. 모델별 usage 분포, 시간대별 usage, 비용 변화를 함께 볼 수 있게 해줘요. (OpenAI 개발자)

즉, 운영에선 두 레벨을 같이 보는 게 좋습니다.

애플리케이션 레벨

  • 어떤 endpoint가 비싼가
  • 어떤 세션 패턴이 긴가
  • RAG top-k를 늘린 뒤 input tokens가 늘었는가

조직/프로젝트 레벨

  • 오늘 전체 비용이 얼마나 올랐는가
  • 어떤 모델 비중이 커졌는가
  • API key / user / project 단위 usage가 어떻게 변했는가

이 둘이 같이 있어야
“코드 수정 하나가 실제 비용에 어떤 영향을 줬는지”를 훨씬 빨리 볼 수 있습니다.


11. Agents SDK를 쓴다면 trace는 거의 필수에 가깝습니다

일반 Responses API만 쓸 때는 로그와 메트릭이 먼저예요.
근데 Agents SDK나 tool-calling 워크플로가 복잡해지면 trace가 정말 중요해집니다.

OpenAI Agents SDK observability 가이드는 tracing이 기본 활성화되고, 각 run에서 model calls, tool calls, handoffs, guardrails, custom spans를 볼 수 있다고 설명합니다. Trace grading 가이드는 Traces 대시보드에서 representative workflow를 보고 grader를 돌려 prompts, tool surfaces, routing logic, guardrails를 개선하라고 안내합니다. (OpenAI 개발자)

이게 왜 좋냐면,
에이전트 구조는 겉보기 응답만 보면 원인을 알기 어려운 경우가 많거든요.

예를 들어:

  • tool selection이 잘못됐는지
  • tool output parsing이 흔들렸는지
  • handoff가 과하게 일어나는지
  • guardrail 때문에 중간에 막혔는지

이건 trace가 있으면 훨씬 빨리 보입니다.


12. RAG에서는 “정확도” 알림도 운영 대상입니다

RAG는 에러가 안 나도 품질이 망가질 수 있습니다.

예를 들어:

  • retrieved chunk 수가 갑자기 줄거나 늘어남
  • source diversity가 낮아짐
  • 항상 같은 파일만 과도하게 뜸
  • top-k는 그대로인데 answer length만 늘어남
  • “문서에 없음” fallback 비율이 급증

Accuracy 가이드는 retrieval에서 wrong context와 too much irrelevant context가 품질을 해칠 수 있다고 말합니다. 즉, RAG는 단순 장애 모니터링이 아니라 검색 품질 이상 징후도 봐야 합니다. (OpenAI 개발자)

그래서 저는 RAG에는 이런 로그도 추천합니다.

  • retrieved_chunk_count
  • unique_source_count
  • top_source_names
  • fallback_used
  • retrieval_ms
  • generation_ms

이걸 보면 “모델이 이상한지”보다 먼저
“검색이 이상해졌는지”를 볼 수 있습니다.


13. latency는 평균보다 p95가 더 중요합니다

운영에서 평균 latency만 보면 꽤 자주 속습니다.

왜냐하면 평균은 괜찮은데,
느린 요청 몇 개가 사용자 경험을 다 망칠 수 있거든요.

OpenAI latency optimization 가이드는 latency를 줄이는 원칙을 별도 주제로 다루고 있고, production best practices도 scaling과 latency를 운영 핵심으로 봅니다. 실무에선 average보다 p95/p99를 같이 보는 게 훨씬 현실적입니다. (OpenAI 개발자)

AI 서비스는 특히 이런 경우가 많아요.

  • 대부분 요청은 1초대
  • 그런데 RAG + 긴 히스토리 + 긴 출력 몇 개가 8초 이상
  • 사용자는 그 8초짜리만 기억함

그래서 대시보드엔 보통 이 정도가 있어야 합니다.

  • p50 latency
  • p95 latency
  • p99 latency
  • endpoint별 latency
  • model별 latency
  • retrieval_ms / generation_ms 분리

14. “비용 이상” 알림도 결국 observability의 일부입니다

이건 은근히 중요합니다.
비용은 회계 항목처럼 보이지만, 실제로는 운영 이상 신호일 때가 많아요.

예를 들어:

  • output tokens가 갑자기 길어짐
  • prompt caching이 안 먹기 시작함
  • model이 예상보다 큰 걸로 바뀜
  • RAG chunk 수가 늘어 input tokens 급증
  • batch 작업이 중복 실행됨

Pricing, cost optimization, prompt caching, usage/cost 자료를 같이 보면
비용은 단순 결제 결과가 아니라 시스템 이상이 숫자로 드러난 것일 수 있습니다. (OpenAI 개발자)

그래서 저는 비용 알림도 기술 알림처럼 다루는 편이 좋다고 생각해요.

  • 하루 비용이 임계치 초과
  • cached input 비율 급락
  • 특정 endpoint 총 토큰 급증
  • 특정 model 비중 급등

이런 건 바로 볼 수 있어야 합니다.


15. 지금 단계에서 추천하는 가장 현실적인 운영 세트

처음부터 Datadog, Grafana, Langfuse, LangSmith, OpenTelemetry 다 붙일 필요는 없습니다.
물론 나중엔 좋지만, 초반엔 오히려 과해질 수 있어요.

제가 추천하는 첫 세트는 이렇습니다.

  1. JSON 구조화 로그
  2. 요청 단위 usage/latency 필드
  3. endpoint별 에러율 집계
  4. p95 latency 대시보드
  5. 비용/usage 일간 체크
  6. RAG 전용 retrieval 메트릭
  7. 에이전트면 trace 사용
  8. 장애 알림 + 비용 이상 알림 분리

이 정도만 있어도 운영 감각이 훨씬 좋아집니다.


16. 자주 하는 실수들

실수 1. 로그는 많은데 request_id가 없다

그러면 한 요청을 끝까지 따라가기가 어렵습니다.

실수 2. 성공 로그를 안 남긴다

실패만 보면 비용/토큰/지연 이상을 놓칩니다.

실수 3. output tokens를 안 본다

지연과 비용 둘 다 여기서 많이 새는데 놓치기 쉽습니다. OpenAI production best practices도 출력 길이가 latency에 큰 영향을 준다고 설명합니다. (OpenAI 개발자)

실수 4. RAG를 일반 채팅처럼만 본다

retrieval 품질 지표를 따로 안 보면 검색 이상을 늦게 봅니다.

실수 5. trace 없이 agent만 복잡하게 만든다

tool call/handoff가 늘어나면 trace 없이는 디버깅이 정말 힘들어집니다. (OpenAI 개발자)


17. 오늘 글의 핵심 요약

이번 글에서 꼭 가져가야 할 건 이것입니다.

운영에서 중요한 건 “기능이 된다”가 아니라, 로그·메트릭·트레이스로 지금 어디가 왜 이상한지 빨리 알아차릴 수 있느냐입니다.

정리하면:

  • OpenAI 운영 가이드는 production best practices, latency optimization, cost optimization, usage/cost 추적을 별도로 다룹니다. (OpenAI 개발자)
  • Usage/Costs API와 cookbook은 모델별·시간대별 usage와 비용 추적 흐름을 제공합니다. (OpenAI 개발자)
  • Agents SDK는 tracing이 기본 활성화되고, model/tool/handoff/guardrail/custom span을 추적할 수 있습니다. (OpenAI 개발자)
  • 따라서 AI 백엔드 observability는 최소한 logs, metrics, traces 세 축으로 설계하는 게 좋습니다. (OpenAI 개발자)

이걸 붙이고 나면 진짜 느낌이 달라집니다.
장애가 안 나는 시스템은 없지만,
이상해졌을 때 빨리 눈치채는 시스템은 만들 수 있거든요.


다음 편 예고

다음 글에서는 여기서 한 단계 더 들어가겠습니다.

Python + FastAPI에서 OpenAI 기능을 팀 개발에 맞게 모듈화하고, 프로젝트 구조를 정리하는 방법

이 주제로,

  • 기능이 늘어날수록 폴더 구조를 어떻게 나눌지
  • services / adapters / repositories / routers를 어디까지 분리할지
  • AI 코드가 커져도 안 엉키는 구조
  • 주니어가 처음부터 피하면 좋은 구조적 실수

까지 이어가보겠습니다.


출처

  • OpenAI Production best practices — 프로토타입에서 프로덕션으로 갈 때 보안, 아키텍처, 비용, 운영을 함께 보라고 안내. (OpenAI 개발자)
  • OpenAI Latency optimization — LLM 애플리케이션 전반의 지연 최적화 원칙 정리. (OpenAI 개발자)
  • OpenAI Cost optimization — 토큰, 요청 수, 모델 선택을 통한 비용 절감 원칙 정리. (OpenAI 개발자)
  • OpenAI Usage API / Costs API cookbook — usage/cost 데이터 수집 및 시각화 예제. (OpenAI 개발자)
  • OpenAI Agents SDK Integrations and observability — tracing 기본 활성화, model/tool/handoff/guardrail/custom span 관측 가능. (OpenAI 개발자)
  • OpenAI Trace grading / Agent evals — Traces 대시보드와 grader를 통한 워크플로 분석 흐름. (OpenAI 개발자)

Python, OpenAI, FastAPI, observability, monitoring, tracing, metrics, structured logging, Usage API, Costs API, Agents SDK, latency optimization, RAG 운영, AI 백엔드 운영, 주니어 개발자

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함
반응형