모델 리뷰 · Moonshot AI

Review: Kimi K3.1 Pro

Moonshot의 새 플래그십 모델은 에이전트 코딩과 사무 작업에서 최상위 상용 모델에 근접했습니다. 가격은 100만 토큰당 $2 / $10이며, 가중치도 공개될 예정입니다.

2026년 10월 23일 · 출시 당일 첫인상 리뷰

총평: K3.1 Pro는 지금까지 발표된 오픈 웨이트 모델 중 가장 강력합니다. Artificial Analysis 지능 지수에서 Claude Opus 5.5에 불과 3점 뒤지면서도 가격은 훨씬 저렴합니다. 특히 터미널·에이전트 작업에서 K3보다 크게 향상됐습니다. 직접 진행한 코딩 테스트에서도 첫 시도에 깔끔하고 정확한 결과물을 냈습니다. 단점은 사고(thinking) 모드를 끌 수 없고, K3보다 토큰을 더 많이 쓰며, 독립 테스트에서 환각률이 여전히 높다는 점입니다.

장점

  • 지능 지수 55점, 전체 4위. 가중치 공개 시 오픈 웨이트 1위
  • Terminal-Bench 4.0 점수 13% → 67%로 급상승 (Artificial Analysis)
  • 100만 토큰당 입력 $2 / 출력 $10 / 캐시 적중 $0.20, K3보다 33% 저렴
  • 100만 토큰 컨텍스트, 긴 컨텍스트 추가 요금 없음

단점

  • 사고 모드 상시 작동. 줄이는 방법은 reasoning_effort: low뿐
  • 작업당 출력 토큰이 K3보다 14% 많음
  • AA-Omniscience 환각률 여전히 44%
  • 가중치 미공개(11월 4일까지 약속), 다수 벤치마크가 자체 발표 수치

어떤 모델인가

Kimi K3.1 Pro는 Moonshot AI의 K3.1 시리즈의 첫 모델입니다. 새로운 베이스 모델이 아니라, Kimi K3에 훨씬 많은 사후 학습(post-training) 연산을 투입한 모델입니다. Moonshot은 이 모델을 자사 최강의 "3T급" 오픈 웨이트 모델이라고 소개합니다. 함께 출시된 K3.1 Pro Highspeed는 2~3배 더 빠릅니다.

2.8T / 104B전체 / 활성 파라미터 (MoE)
100만 토큰컨텍스트 창, 최대 출력 1,048,576 토큰
$2 / $10100만 토큰당 입력 / 출력 · 캐시 적중 $0.20
텍스트·이미지·영상입력 지원 (API에서 영상은 파일 업로드 방식)
2026년 11월 4일까지전체 가중치 공개 예정, 라이선스 미발표
사고 모드 상시 작동reasoning_effort: low · high · max (기본값 max)

파라미터 수와 가격은 Artificial Analysis 자료입니다. Moonshot 블로그에는 파라미터 수가 나오지 않습니다. API 정보는 Kimi 공식 빠른 시작 문서를 참고했습니다.

사용할 수 있는 곳

벤치마크

독립 평가: Artificial Analysis

평가 항목K3.1 ProKimi K3비고
지능 지수5544전체 4위. Opus 5.5(58), Sonnet 5.5(56), Muse Wonder Full(56) 다음
GDPval-AA (Elo)1,8461,5343위. Opus 5.5 1,866, Muse Wonder Full 1,857
Terminal-Bench 4.067%13%측정 모델 중 최고. 2위는 Sonnet 5.5(64%)
AutomationBench-AA73%58%
Humanity's Last Exam52%47%
SciCode61%59%소폭 향상
CritPt25%23%소폭 향상
AA-Omniscience 환각률44%53%개선됐지만 여전히 높음 (낮을수록 좋음)
작업당 비용$1.98비슷토큰 단가는 낮지만 작업당 출력 토큰이 55K로 K3(48K)보다 많음

자체 발표: Moonshot

벤치마크K3.1 Pro비교
Terminal-Bench 4.065.7%Claude Opus 5.5 66.4% · GPT-6 Astra 58.2%
Kimi Code Bench 3.0 (장기 작업)61.2%K3 55.95%
BenchCAD (도구 사용)92.7Opus 5.5 96.2 · GPT-6 Astra 95.9 · Fable 5.1 92.6
OfficeQA Pro v264.4Moonshot은 최고 수준(SOTA)이라고 주장
내부 비즈니스 운영 평가37K3 8.9 (약 4.2배)
Kimi Hallu Bench Hard (도구 사용, 낮을수록 좋음)8.3%K3 23.4% · Opus 5.5 15.0 · GPT-6 Astra 5.5
xdev-bench-hard (사이버 보안, pass@3)81.0사이버 위험 감소 학습 전 90.5

Moonshot은 reasoning effort "max", temperature 1.0, top-p 0.95로 평가했습니다. 환각 수치는 서로 엇갈립니다. Moonshot 자체 스트레스 테스트는 큰 개선을 보여주지만, Artificial Analysis의 Omniscience 테스트에서는 환각률이 여전히 44%입니다. 자체 발표 수치를 그대로 믿기보다는 독립 평가가 더 나오기를 기다리는 편이 좋습니다.

의도적인 사이버 보안 트레이드오프

Moonshot은 오픈 웨이트 공개를 위해 중간 학습과 강화 학습 단계에서 사이버 공격 관련 데이터를 제거하고, 일반 코딩 데이터는 유지했다고 밝혔습니다. 그 결과 익스플로잇 벤치마크 점수가 90.5에서 81.0으로 떨어졌습니다. 오픈 모델로 공격 능력의 최전선을 넓히지 않겠다는 것이 목표입니다. 이례적이면서도 투명하게 공개된 선택입니다. 다만 보안 연구자에게는 전체 순위가 시사하는 것보다 약한 도구가 됩니다.

직접 테스트: 한 번에 끝내는 코딩 과제

여러 모델을 비교할 때 쓰는 동일한 명세를 K3.1 Pro에 주었습니다. 단일 HTML 파일로 완성도 높은 틱택토 게임을 만드는 과제입니다. 명세에는 미니맥스 AI, 정해진 규칙대로 유지되는 점수판, 정확한 격자 크기 CSS, 버튼 색상, 단일 클래스 구조 등이 포함됩니다. 작은 과제지만, 긴 요구 사항 목록을 하나도 빠뜨리지 않고 지키는지 확인할 수 있습니다.

실제 사용한 프롬프트 보기 (원문 그대로)
# Tic-Tac-Toe Game — Build Specification

## Objective
Create a complete, runnable **single-file HTML document** implementing a professional-quality Tic-Tac-Toe game (human **X** vs. AI **O**). The file must be fully playable by opening it in a browser — no build step, no dependencies.

## Core Gameplay
- 3×3 grid of clickable cells; human clicks empty cells, AI responds automatically
- Human is **X**, AI is **O**
- Status label showing whose turn it is and the game result
- Detect and announce wins across rows, columns, and both diagonals, plus draws
- Highlight the winning three cells and disable further moves when the game ends

## Features
- **Difficulty selector:**
  - Easy → AI plays random moves
  - Hard → AI uses the **minimax algorithm** for unbeatable optimal play
- **New Game button** that clears the board only — **the scoreboard is preserved**
- **Scoreboard** tracking human wins, AI wins, and draws
  - Scores accumulate across rounds within the same browser session
  - Scores reset to zero **only when the page is first loaded/refreshed** (a fresh browser start)
  - Do **not** persist scores across page loads (no `localStorage`)

## Visual Design
- Dark slate/charcoal background (subtle deep navy-to-slate gradient — **no pink or pastel backgrounds**)
- Clean elevated card holding the board, with soft shadows and rounded corners
- Vibrant, high-contrast accents:
  - Distinct colors for X and O (e.g. bright cyan for X, warm amber for O)
  - Clear hover effects on cells
  - Generous, clean spacing
- Large readable fonts; fixed, nicely centered layout optimized for a desktop browser

## Grid Sizing (must be uniform)
- All grid cells must be **equal-sized perfect squares** — cell size must **not** change based on whether a cell is empty or filled
- Use a fixed grid structure: `grid-template-columns: repeat(3, 1fr)` with a fixed `max-width` on the board
- Force each cell to a square with `aspect-ratio: 1 / 1`
- Use a **fixed `font-size`** for X/O and center the mark with flexbox (`display: flex; align-items: center; justify-content: center`) so the letter never resizes its cell
- Apply `box-sizing: border-box` so borders/padding don't alter the cell footprint

## Control Buttons
Style the three bottom buttons with distinct, high-contrast backgrounds and bold, readable text:
- **Easy** → green
- **Hard** → red/orange
- **New Game** → blue
- All with white bold lettering
- Use standard CSS buttons (not platform widgets) so colored backgrounds render reliably in every browser

## Code Structure
- All logic in a **single JavaScript class** with separate methods for:
  - Building the UI
  - Handling clicks
  - AI move logic
  - Checking the winner
  - Resetting the board (New Game — does **not** touch the scoreboard)
- Semantic HTML, inline CSS in a `<style>` block, inline JS in a `<script>` block
- **No external files, frameworks, or CDN dependencies** — fully offline-capable
- Include concise comments

## Output
- Output **only** the complete HTML code in a single file
- Save the file as: 24_Kimi_K3.1_Pro.html

결과: 모든 요구 사항 충족

수정하지 않은 원본 결과물 (게임 화면은 모델이 만든 그대로 영어) · 17.8KB · 외부 의존성 없음 전체 화면으로 열기 ↗
모델이 생성한 실제 코드 보기 (원본 그대로, 17.4KB)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="color-scheme" content="dark">
  <title>Tic-Tac-Toe — Human vs AI</title>
  <style>
    *, *::before, *::after {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      min-height: 100svh;
      display: grid;
      place-items: center;
      padding: 32px;
      color: #e5edf7;
      background:
        radial-gradient(circle at 12% 15%, rgba(8, 145, 178, 0.16), transparent 34%),
        radial-gradient(circle at 88% 85%, rgba(30, 64, 175, 0.18), transparent 36%),
        linear-gradient(145deg, #070d17 0%, #0f172a 55%, #182234 100%);
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }

    button {
      font: inherit;
    }

    .game-card {
      width: 100%;
      max-width: 500px;
      padding: 32px 36px 30px;
      border: 1px solid rgba(148, 163, 184, 0.17);
      border-radius: 26px;
      background: linear-gradient(155deg, rgba(27, 39, 56, 0.98), rgba(15, 23, 36, 0.98));
      box-shadow: 0 28px 70px rgba(0, 0, 0, 0.42), 0 4px 16px rgba(0, 0, 0, 0.24);
    }

    .game-header {
      text-align: center;
    }

    .eyebrow {
      margin: 0 0 7px;
      color: #67e8f9;
      font-size: 11px;
      font-weight: 800;
      letter-spacing: 0.2em;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      color: #f8fafc;
      font-size: 38px;
      line-height: 1.15;
      letter-spacing: -0.035em;
    }

    .intro {
      margin: 9px 0 0;
      color: #aab8ca;
      font-size: 15px;
      line-height: 1.5;
    }

    .x-text {
      color: #22d3ee;
    }

    .o-text {
      color: #fbbf24;
    }

    .scoreboard {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 10px;
      margin: 23px 0 18px;
    }

    .score-card {
      padding: 11px 8px 10px;
      border: 1px solid rgba(148, 163, 184, 0.13);
      border-radius: 13px;
      background: rgba(7, 15, 27, 0.42);
      text-align: center;
    }

    .score-label {
      display: block;
      margin-bottom: 2px;
      color: #aab8ca;
      font-size: 11px;
      font-weight: 800;
      letter-spacing: 0.055em;
      text-transform: uppercase;
    }

    .score-value {
      display: block;
      color: #f8fafc;
      font-size: 28px;
      font-weight: 800;
      line-height: 1.15;
      font-variant-numeric: tabular-nums;
    }

    .status {
      min-height: 46px;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 9px;
      margin-bottom: 18px;
      padding: 10px 14px;
      border: 1px solid rgba(103, 232, 249, 0.15);
      border-radius: 12px;
      background: rgba(8, 47, 73, 0.25);
      color: #e2e8f0;
      font-size: 15px;
      font-weight: 650;
      text-align: center;
    }

    .status-dot {
      width: 8px;
      height: 8px;
      flex: 0 0 8px;
      border-radius: 50%;
      background: #22d3ee;
      box-shadow: 0 0 12px rgba(34, 211, 238, 0.65);
    }

    .board {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 12px;
      width: 100%;
      max-width: 360px;
      margin: 0 auto;
    }

    .cell {
      box-sizing: border-box;
      width: 100%;
      min-width: 0;
      aspect-ratio: 1 / 1;
      display: flex;
      align-items: center;
      justify-content: center;
      margin: 0;
      padding: 0;
      border: 1px solid #33445a;
      border-radius: 16px;
      appearance: none;
      -webkit-appearance: none;
      background: linear-gradient(145deg, #1b2a3d, #142032);
      box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 5px 12px rgba(0, 0, 0, 0.15);
      font-size: 64px;
      font-weight: 750;
      line-height: 1;
      cursor: pointer;
      user-select: none;
      transition: transform 140ms ease, border-color 140ms ease, background-color 140ms ease, box-shadow 140ms ease;
    }

    .cell:not(:disabled):hover {
      transform: translateY(-2px);
      border-color: #22d3ee;
      background: #1d3448;
      box-shadow: 0 8px 18px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(34, 211, 238, 0.2);
    }

    .cell:not(:disabled):active {
      transform: translateY(0) scale(0.97);
    }

    .cell:focus-visible,
    .control-button:focus-visible {
      outline: 3px solid #e2e8f0;
      outline-offset: 3px;
    }

    .cell:disabled {
      cursor: default;
      opacity: 1;
    }

    .cell.x {
      color: #22d3ee;
      text-shadow: 0 0 24px rgba(34, 211, 238, 0.24);
    }

    .cell.o {
      color: #fbbf24;
      text-shadow: 0 0 24px rgba(251, 191, 36, 0.22);
    }

    .cell.winner {
      border-color: #a5f3fc;
      background: #1c4052;
      box-shadow: inset 0 0 0 2px rgba(103, 232, 249, 0.26), 0 0 25px rgba(34, 211, 238, 0.25);
      animation: winner-glow 1.35s ease-in-out infinite;
    }

    .controls {
      margin-top: 24px;
    }

    .control-heading {
      display: flex;
      justify-content: space-between;
      gap: 12px;
      margin-bottom: 11px;
      color: #8f9fb3;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.04em;
    }

    .button-row {
      display: grid;
      grid-template-columns: 1fr 1fr 1.25fr;
      gap: 11px;
    }

    .control-button {
      min-height: 46px;
      padding: 12px 10px;
      border: 0;
      border-radius: 11px;
      appearance: none;
      -webkit-appearance: none;
      color: #ffffff;
      font-size: 14px;
      font-weight: 800;
      line-height: 1.2;
      cursor: pointer;
      box-shadow: 0 5px 13px rgba(0, 0, 0, 0.2);
      transition: transform 140ms ease, background-color 140ms ease, box-shadow 140ms ease;
    }

    .control-button:hover {
      transform: translateY(-1px);
      box-shadow: 0 8px 17px rgba(0, 0, 0, 0.27);
    }

    .control-button:active {
      transform: translateY(0);
    }

    .easy {
      background: #15803d;
    }

    .easy:hover {
      background: #166534;
    }

    .hard {
      background: #c2410c;
    }

    .hard:hover {
      background: #9a3412;
    }

    .new-game {
      background: #1d4ed8;
    }

    .new-game:hover {
      background: #1e40af;
    }

    .difficulty[aria-pressed="true"] {
      outline: 3px solid #f8fafc;
      outline-offset: 3px;
    }

    .help-text {
      margin: 15px 0 0;
      color: #8291a5;
      font-size: 12px;
      line-height: 1.45;
      text-align: center;
    }

    .noscript {
      max-width: 460px;
      margin: 20px auto;
      padding: 16px;
      border-radius: 12px;
      background: #7f1d1d;
      color: #ffffff;
      text-align: center;
    }

    @keyframes winner-glow {
      0%, 100% {
        box-shadow: inset 0 0 0 2px rgba(103, 232, 249, 0.22), 0 0 18px rgba(34, 211, 238, 0.18);
      }
      50% {
        box-shadow: inset 0 0 0 2px rgba(103, 232, 249, 0.4), 0 0 30px rgba(34, 211, 238, 0.36);
      }
    }

    @media (max-width: 480px) {
      body {
        padding: 18px;
      }

      .game-card {
        padding: 25px 20px 24px;
        border-radius: 22px;
      }

      h1 {
        font-size: 34px;
      }

      .board {
        gap: 10px;
      }

      .cell {
        border-radius: 14px;
        font-size: 54px;
      }

      .button-row {
        gap: 9px;
      }

      .control-button {
        padding-inline: 7px;
        font-size: 13px;
      }
    }

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        scroll-behavior: auto !important;
        animation: none !important;
        transition: none !important;
      }
    }
  </style>
</head>
<body>
  <main id="game" class="game-card" aria-labelledby="game-title"></main>
  <noscript>
    <p class="noscript">JavaScript is required to play this Tic-Tac-Toe game.</p>
  </noscript>

  <script>
    class TicTacToe {
      constructor(root) {
        this.root = root;
        this.scores = { human: 0, ai: 0, draws: 0 };
        this.difficulty = 'hard';
        this.aiTimer = null;
        this.buildUI();
        this.updateDifficultyControls();
        this.renderScores();
        this.resetBoard();
      }

      // Create the interface and connect all event handlers.
      buildUI() {
        this.root.innerHTML = `
          <header class="game-header">
            <p class="eyebrow">Human vs AI</p>
            <h1 id="game-title">Tic-Tac-Toe</h1>
            <p class="intro">You are <strong class="x-text">X</strong>. The AI is <strong class="o-text">O</strong>. Get three in a row.</p>
          </header>

          <section class="scoreboard" aria-label="Scoreboard">
            <div class="score-card">
              <span class="score-label x-text">You (X)</span>
              <output id="human-score" class="score-value" aria-label="Human wins">0</output>
            </div>
            <div class="score-card">
              <span class="score-label o-text">AI (O)</span>
              <output id="ai-score" class="score-value" aria-label="AI wins">0</output>
            </div>
            <div class="score-card">
              <span class="score-label">Draws</span>
              <output id="draw-score" class="score-value" aria-label="Draws">0</output>
            </div>
          </section>

          <div class="status" role="status" aria-live="polite" aria-atomic="true">
            <span class="status-dot" aria-hidden="true"></span>
            <span id="status-text"></span>
          </div>

          <div id="board" class="board" role="group" aria-label="Tic-tac-toe board"></div>

          <section class="controls" aria-label="Game controls">
            <div class="control-heading">
              <span>AI DIFFICULTY</span>
              <span>NEW GAME KEEPS SCORES</span>
            </div>
            <div class="button-row">
              <button id="easy-button" class="control-button difficulty easy" type="button" aria-pressed="false" title="Easy: the AI chooses random moves">Easy</button>
              <button id="hard-button" class="control-button difficulty hard" type="button" aria-pressed="true" title="Hard: unbeatable minimax AI">Hard</button>
              <button id="new-game-button" class="control-button new-game" type="button">New Game</button>
            </div>
            <p class="help-text">Easy plays randomly. Hard uses minimax for optimal play.</p>
          </section>
        `;

        this.boardElement = this.root.querySelector('#board');
        this.statusText = this.root.querySelector('#status-text');
        this.scoreElements = {
          human: this.root.querySelector('#human-score'),
          ai: this.root.querySelector('#ai-score'),
          draws: this.root.querySelector('#draw-score')
        };
        this.difficultyButtons = {
          easy: this.root.querySelector('#easy-button'),
          hard: this.root.querySelector('#hard-button')
        };
        this.cells = [];

        for (let index = 0; index < 9; index += 1) {
          const cell = document.createElement('button');
          cell.type = 'button';
          cell.className = 'cell';
          cell.dataset.index = index;
          cell.addEventListener('click', () => this.handleClick(index));
          this.boardElement.appendChild(cell);
          this.cells.push(cell);
        }

        this.difficultyButtons.easy.addEventListener('click', () => this.setDifficulty('easy'));
        this.difficultyButtons.hard.addEventListener('click', () => this.setDifficulty('hard'));
        this.root.querySelector('#new-game-button').addEventListener('click', () => this.resetBoard());
      }

      // Apply a human move, then hand the turn to the AI.
      handleClick(index) {
        if (this.gameOver || this.currentPlayer !== 'X' || this.board[index]) {
          return;
        }

        this.board[index] = 'X';
        const result = this.checkWinner();

        if (result) {
          this.finishRound(result);
          return;
        }

        this.currentPlayer = 'O';
        this.setStatus('AI is thinking…');
        this.renderBoard();

        this.aiTimer = window.setTimeout(() => {
          this.aiTimer = null;
          this.makeAIMove();
        }, 400);
      }

      // Choose a random move on Easy or an optimal move on Hard.
      makeAIMove() {
        if (this.gameOver || this.currentPlayer !== 'O') {
          return;
        }

        const available = this.board
          .map((value, index) => value === null ? index : null)
          .filter(index => index !== null);

        if (available.length === 0) {
          return;
        }

        const move = this.difficulty === 'hard'
          ? this.findBestMove()
          : available[Math.floor(Math.random() * available.length)];

        this.board[move] = 'O';
        const result = this.checkWinner();

        if (result) {
          this.finishRound(result);
          return;
        }

        this.currentPlayer = 'X';
        this.setStatus('Your turn — place an X.');
        this.renderBoard();
      }

      // Minimax scores faster wins higher and delays unavoidable losses.
      minimax(board, isMaximizing, depth = 0) {
        const result = this.checkWinner(board);

        if (result) {
          if (result.winner === 'O') return 10 - depth;
          if (result.winner === 'X') return depth - 10;
          return 0;
        }

        let bestScore = isMaximizing ? -Infinity : Infinity;

        for (let index = 0; index < board.length; index += 1) {
          if (board[index] !== null) continue;

          board[index] = isMaximizing ? 'O' : 'X';
          const score = this.minimax(board, !isMaximizing, depth + 1);
          board[index] = null;

          bestScore = isMaximizing
            ? Math.max(bestScore, score)
            : Math.min(bestScore, score);
        }

        return bestScore;
      }

      // Randomly select among equally optimal moves for varied Hard games.
      findBestMove() {
        let bestScore = -Infinity;
        let bestMoves = [];

        for (let index = 0; index < this.board.length; index += 1) {
          if (this.board[index] !== null) continue;

          this.board[index] = 'O';
          const score = this.minimax(this.board, false);
          this.board[index] = null;

          if (score > bestScore) {
            bestScore = score;
            bestMoves = [index];
          } else if (score === bestScore) {
            bestMoves.push(index);
          }
        }

        return bestMoves[Math.floor(Math.random() * bestMoves.length)];
      }

      // Return a winner and its line, a draw, or null if play continues.
      checkWinner(board = this.board) {
        const lines = [
          [0, 1, 2], [3, 4, 5], [6, 7, 8],
          [0, 3, 6], [1, 4, 7], [2, 5, 8],
          [0, 4, 8], [2, 4, 6]
        ];

        for (const line of lines) {
          const [a, b, c] = line;
          if (board[a] && board[a] === board[b] && board[a] === board[c]) {
            return { winner: board[a], line };
          }
        }

        if (board.every(cell => cell !== null)) {
          return { winner: 'draw', line: [] };
        }

        return null;
      }

      // Lock the board and update exactly one score at round end.
      finishRound(result) {
        this.gameOver = true;
        this.currentPlayer = null;
        this.winningLine = result.line;

        if (result.winner === 'X') {
          this.scores.human += 1;
          this.setStatus('You win! X completes three in a row.');
        } else if (result.winner === 'O') {
          this.scores.ai += 1;
          this.setStatus('AI wins! O completes three in a row.');
        } else {
          this.scores.draws += 1;
          this.setStatus('It’s a draw — no moves left.');
        }

        this.renderScores();
        this.renderBoard();
      }

      // Clear only the current round; scores intentionally remain untouched.
      resetBoard() {
        window.clearTimeout(this.aiTimer);
        this.aiTimer = null;
        this.board = Array(9).fill(null);
        this.currentPlayer = 'X';
        this.gameOver = false;
        this.winningLine = [];
        this.setStatus('Your turn — place an X.');
        this.renderBoard();
      }

      setDifficulty(difficulty) {
        if (difficulty !== 'easy' && difficulty !== 'hard') return;
        this.difficulty = difficulty;
        this.updateDifficultyControls();
      }

      updateDifficultyControls() {
        for (const [difficulty, button] of Object.entries(this.difficultyButtons)) {
          button.setAttribute('aria-pressed', String(this.difficulty === difficulty));
        }
      }

      setStatus(message) {
        this.statusText.textContent = message;
      }

      renderScores() {
        for (const [key, element] of Object.entries(this.scoreElements)) {
          element.textContent = this.scores[key];
        }
      }

      // Synchronize marks, square sizes, labels, highlighting, and availability.
      renderBoard() {
        const canPlay = !this.gameOver && this.currentPlayer === 'X';

        this.cells.forEach((cell, index) => {
          const mark = this.board[index];
          const row = Math.floor(index / 3) + 1;
          const column = (index % 3) + 1;

          cell.textContent = mark || '';
          cell.classList.toggle('x', mark === 'X');
          cell.classList.toggle('o', mark === 'O');
          cell.classList.toggle('winner', this.winningLine.includes(index));
          cell.disabled = !canPlay || mark !== null;
          cell.setAttribute('aria-label', `Row ${row}, column ${column}: ${mark || 'empty'}`);
        });
      }
    }

    new TicTacToe(document.getElementById('game'));
  </script>
</body>
</html>

비용

이 테스트는 코딩 도구 OpenCode에서 OpenRouter API를 통해 한 번만 실행했습니다. 모델은 요청 12회 동안 파일을 쓰고 확인한 뒤 작업을 마쳤고, 걸린 시간은 약 4분이었습니다.

$0.41총비용 (게임 1개 완성)
12회API 요청 수
약 4분오후 12:40 ~ 12:44
364,499 / 25,850입력 / 출력 토큰

금액은 OpenRouter 사용 기록 기준입니다. 정가($2 / $10)로 계산하면 약 $0.99이지만, 실제 청구액은 $0.41이었습니다. 에이전트가 매 요청마다 이전 대화를 다시 보내기 때문에 입력 대부분에 캐시 요금($0.20)이 적용된 것으로 보입니다. 응답 속도는 초당 31~98토큰, 첫 토큰까지는 2~5.6초였습니다.

코드 리뷰: 솔직한 평가

한 줄 요약: 결과물은 훌륭하지만, 이 과제만으로는 모델에 대해 알 수 있는 것이 많지 않습니다. 틱택토는 최상위 모델이라면 어느 것이든 만들 수 있습니다. 이 결과물이 보여주는 것은 K3.1 Pro가 길고 세세한 지시를 매우 꼼꼼히 따르고 엔지니어링 판단력도 좋다는 점입니다. 어려운 문제를 어떻게 다루는지는 보여주지 못합니다.

이 결과물의 점수: A (10점 만점에 약 9점)

인상적인 점

실제 약점 (작지만 분명한)

이 테스트가 말해주지 않는 것

여러 단계에 걸친 긴 코딩 작업, 디버깅, 대규모 코드베이스, 응답 속도는 이 테스트로 알 수 없습니다. 모델 간 차이가 실제로 드러나는 곳은 바로 이런 영역입니다. 게다가 K3.1 Pro는 사고 모드를 끌 수 없어 응답이 느릴 수 있습니다. 코딩 모델로서 제대로 평가하려면 다중 파일 리팩터링이나 숨겨둔 버그 수정 같은 더 어려운 과제가 필요합니다. 이런 영역은 위의 벤치마크가 대신 보여줍니다.

누구에게 맞는가

결론

K3.1 Pro는 Artificial Analysis 지수에서 오픈 모델과 상용 모델의 격차를 12점에서 약 3점으로 좁혔고, 가격은 100만 토큰당 $2 / $10입니다. 가중치가 약속대로 쓸 만한 라이선스로 공개된다면 코딩 에이전트용 오픈 모델의 기본 선택지가 될 것입니다. 그때까지는 몇 가지 아쉬운 점이 있지만 뛰어나고 저렴한 API 모델입니다.

출처

  1. Kimi K3.1 Pro 공식 기술 블로그 (Moonshot AI)
  2. Kimi K3.1 Pro 출시 벤치마크 분석 (Artificial Analysis)
  3. Kimi K3.1 Pro API 빠른 시작 문서 (Kimi API 플랫폼)
  4. Kimi K3.1 Pro 모델 페이지 (OpenRouter)
  5. Moonshot의 사이버 능력 축소 관련 보도 (The Next Web)

다운로드

게임 코드24_Kimi_K3.1_Pro.html · 17.4KBKimi K3.1 Pro가 생성한 원본 그대로의 파일 프롬프트24_prompt.txt · 3.0KB테스트에 실제 사용한 프롬프트 전체 묶음Kimi_K3.1_Pro_review_files.zip · 6.6KB게임 코드와 프롬프트를 한 번에