CodeGym /행동 /ChatGPT Apps /비용 통제와 cost 인스트루멘테이션

비용 통제와 cost 인스트루멘테이션

ChatGPT Apps
레벨 19 , 레슨 0
사용 가능

1. 왜 “작동한다” ≠ “수익이 난다”인가

LLM 애플리케이션에는 중요한 특징이 있습니다. 고정 호스팅 비용 외에도 모델 호출과 관련된 일부 요청 실행에 대한 변동 비용이 자주 발생한다는 점입니다.

두 가지 세계를 구분하는 것이 중요합니다:

  • 모델이 ChatGPT 측에서 동작할 때(사용자가 ChatGPT에서 여러분의 App과 대화하고, 그 앱이 mcp-tools를 호출하는 경우) — 토큰 비용은 사용자의 ChatGPT 구독으로 사용자가 지불합니다.
  • 여러분의 backend/MCP 서버가 직접 OpenAI API 또는 다른 LLM 서비스를 호출할 때 — 이 토큰 비용은 여러분이 지불합니다.

바로 두 번째 경우에 고전적인 LLM 변동 비용이 생기며, 이는 요청의 수와 “무게”(tokens_in/tokens_out)에 따라 달라집니다.

전형적 시나리오:

  1. 여러분은 GiftGenius를 프로덕션에 기쁘게 론칭하고, 모든 것이 빠르고, 사용자는 만족해합니다.
  2. 한 달 후 OpenAI + 클라우드 + Stripe 수수료 청구서가 도착하고, “성공적인 성장”이 실제로는 “우리가 각 선물당 벌어들이는 것보다 더 많이 지출한다”는 뜻임을 갑자기 알게 됩니다.

FinOps 접근(FinOps)은 말합니다: 비용은 latency나 error rate와 동일한 메트릭입니다. 이를 로깅하고, 집계하고, 그 결과로 의사결정을 내려야 하며, “엑셀에서 추측”해서는 안 됩니다.

이 강의의 목표는 다음과 같은 질문에 답할 수 있도록 하는 것입니다:

  • user42 사용자를 위한 이 특정 선물 추천은 얼마가 들었나?”
  • “이번 주에 suggest_gifts 도구가 얼마를 태웠고, 그로 인해 얼마나 많은 주문을 가져왔나?”

그리고 그 답이 근거 없는 추정이 아니라 로그와 메트릭에서 나오도록 하는 것입니다.

2. ChatGPT App의 비용 구조

먼저 비용 지도를 그려봅시다. 이것 없이 나머지는 숫자들의 혼란스러운 모음일 뿐입니다.

LLM 비용(변동)

이는 여러분의 백엔드에서 모델을 호출하는 것과 관련된 모든 것입니다:

  • MCP 서버 또는 에이전트에서의 OpenAI 모델 호출: GPT-5.1 / GPT-5-mini / embeddings / rerank / vision / TTS/STT 등.
  • 추가 모델: 검색을 위한 reranking, 추천을 위한 임베딩, 이미지 생성.

중요한 포인트: Apps SDK로 인터페이스를 만들고 ChatGPT 내장 모델만 사용할 때는 토큰 비용을 지불하지 않습니다 — 사용자가 자신의 ChatGPT 구독으로 지불합니다. 하지만 MCP 서버가 직접 OpenAI API(Agents, Responses API, embeddings 등)를 호출하기 시작하는 순간, 토큰은 여러분의 계정에서 나갑니다.

기본 아이디어: 이러한 호출의 비용은 tokens_intokens_out에 비례하며, 각 토큰당 가격을 곱합니다.

MCP tool 호출 자체는 토큰 관점에서 개발자에게 무료입니다. 비용은 그 핸들러 안에서 OpenAI API나 다른 LLM을 호출하기로 결정하는 곳에서만 발생합니다.

인프라

주변의 모든 하드웨어와 서비스:

  • MCP 서버: Vercel / AWS / GCP / bare metal.
  • 에이전트(별도 서비스로 돌아간다면).
  • 데이터베이스: Postgres/MySQL, 벡터 DB, S3/오브젝트 스토리지.
  • 캐시: Redis/KeyDB.
  • 큐와 워커: 예: 백그라운드 생성, 피드 재계산 등.

이러한 비용은 대개 월 단위 고정(혹은 계단형 고정)에 가깝기 때문에, 보통 각 요청 단위가 아니라 클라우드 비용의 집계 데이터를 기준으로 계산합니다.

결제 및 외부 서비스

GiftGenius에는 ACP/Stripe가 있으므로 다음이 발생합니다:

  • 각 성공 결제에 대한 수수료(Stripe는 수% + 고정 수수료 수준).
  • 부정 결제와 차지백으로 인한 손실.
  • 외부 API 비용: e‑mail / SMS / push 알림, 추가 분석 등.

초기에는 미미하지만, 규모가 커지면 체감되기 시작하므로 최소한 로그와 리포트 수준에서라도 분리해두는 것이 좋습니다.

기억을 위한 작은 표

카테고리 예시 대략 계산 방법
LLM GPT‑5.1, GPT‑5‑mini, embeddings, rerank
tokens_in/out × price_per_token
인프라 MCP, Agents, DB, Redis, 큐, CDN 프로바이더 청구서를 트래픽/기간으로 안분
결제와 서비스 Stripe, e‑mail API, SMS, 분석 이벤트 수 × 요금/수수료

우리의 목표: 이 카테고리들을 시스템의 구체적 이벤트 (tool 호출, 워크플로, 체크아웃)에 연결하고, 월말의 최종 합계만 보지 않는 것입니다.

3. usage 데이터를 어디서 수집할까: 세 가지 레이어

비용을 “월 1회”가 아니라 실시간에 가깝게 계산하려면 코드에 인스트루멘테이션을 심어야 합니다. 위치는 총 세 곳입니다.

MCP 서버: 각 도구 호출

MCP 서버는 ChatGPT가 여러분의 tools를 호출하는 자연스러운 지점입니다. 여기서 우리는 다음을 할 수 있습니다:

  • 호출 시작/끝 시점을 포착.
  • duration_ms(또는 latency_ms) 측정.
  • OpenAI 응답에서 토큰 수집(MCP가 여러분의 모델을 호출하는 경우) 또는 최소한 추정.
  • 로그 연결을 위한 user_id, tenant_id, request_id/trace_id 설정.

GiftGenius의 tool_invocation 로그 이벤트는 대략 다음과 같습니다:

{
  "timestamp": "2025-11-20T12:34:56Z",
  "level": "info",
  "event": "tool_invocation",
  "request_id": "abc123",
  "user_id": "user42",
  "service": "mcp-giftgenius",
  "tool_name": "suggest_gifts",
  "tokens_in": 120,
  "tokens_out": 350,
  "cost_estimate_usd": 0.045,
  "latency_ms": 320
}

이제 같은 내용을 TypeScript 타입과 코드로 보겠습니다.

// types/telemetry.ts
export interface ToolInvocationLog {
  event: 'tool_invocation';
  requestId: string;
  userId?: string;
  toolName: string;
  tokensIn?: number;
  tokensOut?: number;
  costEstimateUsd?: number;
  latencyMs: number;
}
// mcp/logger.ts
export function logToolInvocation(payload: ToolInvocationLog) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level: 'info',
    ...payload,
  }));
}

이제 MCP 도구 핸들러(예: suggest_gifts)에 대한 래퍼입니다.

// mcp/tools/suggestGifts.ts
export async function handleSuggestGifts(ctx: Context, input: Input) {
  const started = Date.now();

  const llmResult = await callGiftModel(input); // 여기서 OpenAI 호출

  const duration = Date.now() - started;
  const { prompt_tokens, completion_tokens } = llmResult.usage ?? {};
  const costEstimate = estimateCost(prompt_tokens, completion_tokens);

  logToolInvocation({
    event: 'tool_invocation',
    requestId: ctx.requestId,
    userId: ctx.userId,
    toolName: 'suggest_gifts',
    tokensIn: prompt_tokens,
    tokensOut: completion_tokens,
    costEstimateUsd: costEstimate,
    latencyMs: duration,
  });

  return llmResult.output;
}

토큰을 텍스트 길이로 “대략” 추정하더라도, 아무 것도 없는 것보다는 훨씬 낫습니다.

에이전트 레벨(Agents SDK): 워크플로 단계

Agents SDK를 사용하면 에이전트가 여러 tools를 연속으로 호출할 수 있습니다. 여기서는 에이전트가 어떤 문제를 해결하려는지 단계 컨텍스트를 로깅하는 것이 중요합니다.

예를 들어, 에이전트 러너의 각 tool 호출에 workflow_namestep_name 필드를 추가할 수 있습니다: “아이디어 검색”, “예산별 필터링”, “체크아웃 준비” 등.

이렇게 하면 도구별뿐 아니라 시나리오의 단계별로도 리포트를 만들 수 있습니다. 전체 비용의 80%가 쓸모없는 “추가 보정 단계”에 들어가고 있을 수도 있습니다.

에이전트 주변의 작은 “hook” 예시:

// agents/logStep.ts
export function logAgentStep(data: {
  requestId: string;
  workflow: string;
  step: string;
  toolName: string;
}) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level: 'info',
    event: 'agent_step',
    ...data,
  }));
}

그리고 러너에서 이렇게 사용합니다:

// agents/giftAgent.ts
logAgentStep({
  requestId: run.requestId,
  workflow: 'gift_selection',
  step: 'rank_candidates',
  toolName: 'rerank_gifts',
});

커머스: 체크아웃과 결제

커머스 레이어에서 관심 있는 이벤트는 다음과 같습니다:

  • checkout_started — 구매 시작.
  • checkout_success — 결제 성공.
  • checkout_failed — 오류(코드/유형).

그리고 여기에 다음을 연결해야 합니다:

  • amount, currency.
  • request_idtool_invocation과 동일 세션의 것.

그러면 “이 구매는 LLM 비용으로 N센트가 들었고, M달러의 매출을 가져왔다”라는 질문에 답할 수 있습니다.

간단한 체크아웃 이벤트 핸들러 예시:

// api/commerce/logCheckout.ts
export function logCheckoutEvent(e: {
  type: 'checkout_started' | 'checkout_success' | 'checkout_failed';
  requestId: string;
  userId?: string;
  amountCents?: number;
  currency?: string;
  errorCode?: string;
}) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level: 'info',
    service: 'commerce',
    ...e,
  }));
}

4. 비용용 구조화 로그(M17과의 연결)

핵심 포인트: 아래와 같은 “자유 형식” 텍스트 로그는 금지 console.log("Tool suggest_gifts used 123 tokens"). 모든 것은 JSON으로.

모듈 17에서 이미 request_id, user_id, tool_name 등의 기본 필드로 JSON 형태의 요청 로깅을 합의했습니다. 이제 그 위에 비용 관련 필드를 추가합니다.

비용 관련 로그에 반드시 포함되어야 할 필드:

  • timestamp, level.
  • event(tool_invocation, agent_step, checkout_success 등).
  • request_id, trace_id — 하나의 워크플로 이벤트 체인을 연결하기 위해.
  • user_id, tenant_id — 사용자/회사 단위 집계를 위해.
  • tool_name / service.
  • tokens_in, tokens_out, cost_estimate_usd.
  • latency_ms, success/error_code.

예시에서는 비용 필드를 cost_estimate_usd(미국 달러 기준 비용)로 부르고, 코드와 대시보드에서도 이 이름을 일관되게 사용합니다.

이러한 구조는 다음을 가능하게 합니다:

  • tool_name, user_id, workflowcost_estimate_usd 평균 등 집계.
  • “비싼” 요청을 높은 대기 시간이나 오류와 상관 분석하여, 무엇을 먼저 최적화할지 결정.

이미 모듈 17에서 기본 logger.info({...})를 구현했다면, 비용 필드 추가는 새로운 프레임워크가 아니라 객체에 속성을 몇 개 더 넣는 수준입니다.

5. 코드에서 LLM 비용을 대략 계산하는 법

공식은 전혀 어렵지 않습니다. 마지막 소수점까지 청구서와 일치할 필요는 없고 대략적인 규모만 알면 됩니다.

OpenAI 응답의 usage 사용

여러분의 MCP 서버가 OpenAI Response API를 호출하면 보통 usage 객체를 받습니다:

{
  "usage": {
    "prompt_tokens": 120,
    "completion_tokens": 350,
    "total_tokens": 470
  }
}

이를 바탕으로 비용을 계산하기 좋습니다. 모델마다 입력/출력 100만 토큰당 가격이 다릅니다.

아주 간단한 TypeScript 비용 추정 함수:

// mcp/cost.ts
type Usage = { prompt_tokens?: number; completion_tokens?: number };

const PRICING = {
  inputPerMillion: 2.5,   // 100만 입력 토큰당 달러(예시)
  outputPerMillion: 10.0, // 100만 출력 토큰당 달러(예시)
};

export function estimateCost(
  promptTokens?: number,
  completionTokens?: number,
): number {
  const inTokens = promptTokens ?? 0;
  const outTokens = completionTokens ?? 0;

  const inputCost = (inTokens / 1_000_000) * PRICING.inputPerMillion;
  const outputCost = (outTokens / 1_000_000) * PRICING.outputPerMillion;
  return Number((inputCost + outputCost).toFixed(6)); // 약간 반올림
}

여기 가격은 예시이며, 실제 가격은 최신 OpenAI 가격표에서 가져와 설정 파일에 두면 됩니다. 중요한 점은 이 함수가 각 tool 호출마다 실행되고, 결과가 로그의 cost_estimate_usd 필드에 들어간다는 것입니다.

usage를 사용할 수 없을 때

때로는 usage를 제공하지 않는 외부 LLM을 사용하거나 실제 호출 전 사전 통제가 필요할 수 있습니다. 이때는 다음을 사용할 수 있습니다:

  • tiktoken 같은 라이브러리나 대상 모델용 유사 라이브러리로 토큰 수를 추정.
  • 히스토리 로그의 평균값(median_tokens_in/median_tokens_out)을 도구별로 잡아 가격을 곱함.

길이 추정용 간단한 코드:

// mcp/costEstimateFallback.ts
export function roughTokenEstimate(text: string): number {
  // 대략 추정: 1 토큰 ≈ 라틴 문자 4자
  return Math.ceil(text.length / 4);
}

로켓 과학이 아니지만, 예를 들어 200000 토큰짜리 프롬프트가 저가 요금제로 들어오지 않도록 막는 데 도움이 됩니다.

6. 핵심 비용 메트릭

수집된 로그는 원자재입니다. 이제 여기서 어떤 집계가 실질적으로 유용한지 살펴봅니다.

cost_per_tool_call

정의: 특정 도구 한 번 호출의 평균 비용.

목적:

  • 어떤 도구가 특히 비싼지 보입니다.
  • avg_cost_per_call이 높은데 시나리오 성공 전환은 낮은 “비싸고 쓸모없는” 대상을 찾을 수 있습니다.

로그 기준 계산:

  • 기간 내 event = "tool_invocation" 로그를 가져옵니다.
  • tool_name으로 그룹화합니다.
  • 각 그룹에 대해 avg(cost_estimate_usd)와 비용의 p95(95번째 퍼센타일)를 계산할 수 있습니다.

cost_per_successful_task(또는 cost_per_workflow)

Task/workflow는 사용자 관점에서 완료된 시나리오입니다:

  • GiftGenius의 경우 “선물 추천 + 카드 표시 + 사용자가 N개의 아이디어 저장” 또는 “추천 → 체크아웃 → 결제 성공”이 될 수 있습니다.

방법:

  • workflow 종료 시 workflow_completed 이벤트를 request_id, workflow_name, 성공 플래그와 함께 기록합니다.
  • request_id를 통해 해당 workflow의 모든 tool_invocation을 모아 cost_estimate_usd를 합산합니다.

이렇게 “한 번의 성공한 작업이 얼마 들었는가”를 구할 수 있고 — 시나리오의 원가를 이해하는 핵심 지표가 됩니다.

cost_per_user / cost_per_tenant

B2B 시나리오에서는 “한 사용자/한 팀이 한 달에 우리에게 얼마나 드는가?”가 자주 중요합니다.

계산:

  • tool_invocation 및 기타 비용 이벤트를 user_id 또는 tenant_id별로 그룹화.
  • 기간(일/월) 동안 cost_estimate_usd 합산.

그 다음 이를 요금제 가격과 비교합니다. 만약 cost_per_user가 요금제 가격에 크게 근접한다면, 가격을 올리거나 usage를 최적화해야 할 시점입니다(이 부분은 다음 강의에서 pricing과 “비용 ↔ 품질” 실험을 다룹니다).

7. 예시: GiftGenius용 tool_invocation 포맷과 대시보드

이제 계획 연습에서 했던 것처럼: 로그 이벤트를 설계하고 도구 중심의 최소 대시보드를 만들어봅니다.

GiftGenius의 tool_invocation 이벤트 포맷

앞서 MCP 도구용 최소 로그를 살펴보았습니다. 이제 프로덕션과 대시보드에서 바로 쓸 수 있는 좀 더 상세한 tool_invocation 이벤트를 설계해 봅시다. 아이디어는 동일하며, 서비스/오류/모델 연결 필드가 추가되었을 뿐입니다.

먼저 TypeScript 타입:

// telemetry/events.ts
export interface ToolInvocationEvent {
  timestamp: string;
  level: 'info' | 'error';
  event: 'tool_invocation';
  service: 'mcp-giftgenius';
  requestId: string;
  traceId?: string;
  userId?: string;
  tenantId?: string;
  toolName: string;
  modelId?: string;
  tokensIn?: number;
  tokensOut?: number;
  costEstimateUsd?: number;
  latencyMs: number;
  success: boolean;
  errorCode?: string;
}

그리고 편의 helper:

// telemetry/emitToolInvocation.ts
export function emitToolInvocation(e: ToolInvocationEvent) {
  console.log(JSON.stringify(e));
  // 실서비스에서는 Logtail/Datadog/ELK 등으로 전송
}

각 도구(예: suggest_gifts, rerank_gifts, fetch_catalog)의 핸들러 끝에서(또는 finally 블록에서, 오류가 있어도 로그를 남기도록) emitToolInvocation을 호출합니다.

가장 간단한 도구별 대시보드

최소한의 테이블(예: Metabase / Grafana / 기타 BI):

컬럼 설명
tool_name
도구 이름(suggest_gifts, checkout_create_session 등)
% 트래픽
전체 tool_invocation 중 이 도구가 차지하는 비중
avg_cost_per_call
cost_estimate_usd 기반의 호출 1회 평균 비용
error_rate
success = false인 이벤트 비율
avg_latency_ms
평균 지연 시간
avg_revenue_per_call
이 도구와 연관된 평균 매출(있을 경우)

시각화는 보통 위에 표, 아래에 그래프 두 개 정도로 구성합니다:

  • 막대 차트: X축 = tool_name, Y축 = avg_cost_per_call.
  • 산점도: X = avg_cost_per_call, Y = error_rate 또는 conversion_to_checkout.

이런 그래프는 최적화 후보를 빠르게 찾게 해줍니다: 비싸고, 느리고, 전환도 없다 — 먼저 거기부터.

비용과 매출을 연결하는 데는 checkout_*request_id와 함께 로깅하는 것이 도움이 됩니다. 그러면 avg_revenue_per_call을 해당 도구 호출 수로 매출 합계를 나눈 값으로 계산할 수 있습니다(해당 시나리오에서 checkout_success가 발생한 경우).

8. 인프라 비용 반영(과하지 않게)

LLM 비용은 예쁘게 떨어집니다: 각 호출에 토큰이 있고, 로그에서 바로 비용을 계산할 수 있으니까요. 인프라는 그렇게 간단하지 않습니다: Vercel, DB, Redis 등에 대한 월간 청구서가 있을 뿐이죠.

초기에는 간단한 접근을 권합니다:

  1. 한 달 인프라 총 청구서를 가져옵니다(예: 200$).
  2. 한 달간의 워크플로 수(workflow_completed)로 나눕니다 — 대략적인 infra_cost_per_task를 얻습니다.
  3. 또는 활성 사용자 수로 나눕니다 — infra_cost_per_user.

이 숫자들을 로그로 상세 계산한 LLM 비용과 합치면, 시나리오 또는 사용자별 대략적인 전체 원가를 얻습니다.

앱이 성장하면(서비스/도구별로 비용을 배분하는 등) 더 정교하게 할 수 있지만, 초기 버전에는 이 정도로도 충분히 “깜깜이”를 피할 수 있습니다.

9. GiftGenius의 간단한 end‑to‑end 예시

모든 것을 작은 이야기로 묶어보겠습니다.

사용자가 선물 수령자에 대해 설명하고, ChatGPT가 GiftGenius를 켜도록 제안합니다. 이후:

  1. 위젯이 "gift_selection" 워크플로를 시작합니다.
  2. 여러분의 백엔드는 선물 추천을 더 똑똑하게 하기 위해 LLM 에이전트를 사용하기로 결정합니다.
  3. 에이전트가 3단계를 수행합니다:
  • analyze_recipient(LLM으로 설명 분석).
  • suggest_gifts(우리 MCP 도구).
  • rerank_gifts(리스트 개선을 위한 추가 모델).
  1. 사용자는 선물 카드들을 보고, 몇 가지 아이디어를 저장합니다.
  2. “구매”를 누르면 ACP와 checkout_create_session이 시작됩니다.
  3. checkout_success79.00 USD 금액으로 성공합니다.

로그에 남는 것은 다음과 같습니다:

  • 세 개의 tool_invocation(각각 자신의 tokens_in/tokens_out, cost_estimate_usd, latencyMs 포함).
  • workflow = "gift_selection", step_name이 있는 몇 개의 agent_step.
  • checkout_startedcheckout_success(amount=7900, currency="USD").

request_id로 이것들을 연결하면 다음과 같이 말할 수 있습니다:

  • 시나리오의 LLM 비용: 세 도구의 cost_estimate_usd 합 — 예를 들어 0.19$.
  • 인프라 몫(집계 기준)은 워크플로 하나당 대략 0.03$.
  • 총 원가 0.22$.
  • 거래 매출 — 79$에서 Stripe 수수료 등 차감.

이제 이것은 “GPT‑4는 비싸 보인다”가 아닌 구체적인 유닛 이코노믹스입니다.

10. 비용 인스트루멘테이션에서 흔한 실수

오류 №1: 월간 청구서만 보고 세분화가 없다.
OpenAI/클라우드의 전체 청구서만 보는 것은 유혹적입니다. 하지만 tool_name, user_id, workflow와의 연결이 없으면 정확히 어디에서 돈이 쓰이는지 알 수 없습니다. 결국 최적화는 고비용 시나리오를 정밀하게 개선하는 대신 “모델을 무작정 내리는” 작업이 되어버립니다.

오류 №2: 비용 데이터를 구조 없이 텍스트 로그로만 쓴다.
"Tool suggest_gifts used 123 tokens" 같은 라인은 제대로 집계/필터링하기 어렵습니다. 어느 순간 JSON으로 마이그레이션해야 한다는 것을 깨닫게 될 것이고, 이 이주는 고통스러울 수 있습니다. 처음부터 request_id, tool_name, tokens_in/tokens_out, cost_estimate_usd 필드가 있는 구조화 로그로 작성하세요.

오류 №3: 비용 ↔ 커머스 이벤트의 연결을 무시한다.
checkout_successrequest_id 없이, tool 호출과의 연결 없이 로깅하는 것은 어떤 시나리오가 이익을 내고 어떤 시나리오가 토큰만 쓰는지 이해하지 않겠다는 뜻입니다. 위젯에서 ACP까지 request_id를 끝까지 전달하는 수고를 아끼지 마세요.

오류 №4: 실용적 추정 대신 “완벽한” 빌링을 만들려 한다.
일부 팀은 마지막 토큰까지 OpenAI 청구를 완벽히 재현하려다 함정에 빠집니다. 현실적으로는 대략적인 규모면 충분합니다. 시나리오가 0.02$인지 0.021$인지는 본질적이지 않습니다. 중요한 것은 2$가 아니라는 점입니다. usage 기반의 추정이나 거친 휴리스틱을 두려워하지 마세요.

오류 №5: 비용만 보고 품질을 잊는다.
예쁜 절감 숫자를 보면 모든 곳에서 가장 싼 모델로 바꾸고 싶어질 수 있습니다. 그러다 보면 사용자가 떠나버릴 수준으로 앱을 “최적화”할 수도 있습니다. 비용은 응답 품질과 전환과 함께 봐야 합니다 — 이 연계는 같은 모듈의 다음 강의(가격 책정과 “비용 ↔ 품질” 실험)에서 다룹니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION