> ## Documentation Index
> Fetch the complete documentation index at: https://wb-21fd5541-docs-hivemind-launch.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Evaluation Overview

> 애플리케이션을 체계적으로 개선하기 위한 Evaluation 기반 LLM 애플리케이션 개발

\_Evaluation 기반 LLM 애플리케이션 개발\_은 일관되고 엄선된 예시를 사용해 동작을 체계적으로 측정함으로써 LLM 애플리케이션을 지속적으로 개선할 수 있도록 도와줍니다.

<Tabs>
  <Tab title="Python">
    Weave에서 워크플로의 핵심은 \_`Evaluation` 객체\_이며, 다음을 정의합니다:

    * 테스트 예시를 위한 [`Dataset`](../core-types/datasets) 또는 딕셔너리 목록.
    * 하나 이상의 [채점 함수](../evaluation/scorers).
    * [입력 전처리](#format-dataset-rows-before-evaluating)와 같은 선택 설정.

    `Evaluation`을 정의하고 나면, 이를 [`Model`](../core-types/models) 객체나 LLM 애플리케이션 로직을 포함한 맞춤형 함수에 대해 실행할 수 있습니다. `.evaluate()`를 호출할 때마다 \_평가 run\_이 시작됩니다. `Evaluation` 객체를 설계도라고 생각하면, 각 run은 해당 설정에서 애플리케이션이 얼마나 잘 동작하는지를 측정한 결과라고 볼 수 있습니다.
  </Tab>

  <Tab title="TypeScript">
    Weave에서 워크플로의 핵심은 \_`Evaluation` 객체\_이며, 다음을 정의합니다:

    * 테스트 예시를 위한 [`Dataset`](../core-types/datasets) 또는 객체 배열.
    * 하나 이상의 [채점 함수](../evaluation/scorers).

    `Evaluation`을 정의하고 나면, 이를 `weave.op`으로 래핑된 모든 함수에 대해 실행할 수 있습니다. `.evaluate()`를 호출할 때마다 \_평가 run\_이 시작됩니다. `Evaluation` 객체를 설계도라고 생각하면, 각 run은 해당 설정에서 애플리케이션이 얼마나 잘 동작하는지를 측정한 결과라고 볼 수 있습니다.

    <Note>
      TypeScript SDK는 함수 기반 모델과 scorer를 사용합니다. 클래스 기반 `Model` 및 `Scorer` 유형은 아직 TypeScript에서 사용할 수 없습니다.
    </Note>
  </Tab>
</Tabs>

평가를 시작하려면 다음 step을 완료하세요:

1. [`Evaluation` 객체 생성](#1-create-an-evaluation-object)
2. [예시 테스트 데이터셋 정의](#2-define-a-dataset-of-test-examples)
3. [채점 함수 정의](#3-define-scoring-functions)
4. [평가할 모델 또는 함수 정의](#4-define-a-model-or-function-to-evaluate)
5. [평가 실행](#5-run-the-evaluation)

전체 평가 코드 샘플은 [여기](#full-evaluation-code-sample)에서 확인할 수 있습니다. [저장된 뷰](#saved-views) 및 [명령형 평가](#imperative-evaluations-evaluationlogger)과 같은 [고급 평가 기능](#advanced-evaluation-usage)에 대해서도 자세히 알아볼 수 있습니다.

<div id="1-create-an-evaluation-object">
  ## 1. `Evaluation` 객체 만들기
</div>

`Evaluation` 객체를 만드는 것은 평가 설정을 구성하는 첫 번째 step입니다. `Evaluation`은 예제 데이터, 점수 산정 로직, 그리고 선택적 전처리로 구성됩니다. 이후 이를 사용해 하나 이상의 평가를 실행합니다.

Weave는 각 예제를 애플리케이션에 전달한 뒤 여러 맞춤형 점수 함수로 출력 결과를 평가합니다. 이렇게 하면 애플리케이션의 성능을 확인할 수 있는 뷰와, 개별 출력과 점수를 자세히 살펴볼 수 있는 풍부한 UI를 활용할 수 있습니다.

<div id="optional-custom-naming">
  ### (선택) 맞춤형 이름 지정
</div>

<Tabs>
  <Tab title="Python">
    평가 흐름에서는 맞춤 설정할 수 있는 이름이 두 가지 있습니다:

    * [*Evaluation 객체 이름* (`evaluation_name`)](#name-the-evaluation-object): 설정한 `Evaluation` 객체에 대한 지속적으로 유지되는 레이블입니다.
    * [*Evaluation run 표시 이름* (`__weave["display_name"]`)](#name-individual-evaluation-runs): UI에 표시되는 특정 평가 실행의 레이블입니다.

    #### `Evaluation` 객체 이름 지정

    `Evaluation` 객체 자체의 이름을 지정하려면 `Evaluation` 클래스에 `evaluation_name` 매개변수를 전달하세요. 이 이름은 코드와 UI 목록에서 Evaluation을 파악하는 데 도움이 됩니다.

    ```python lines theme={null}
    evaluation = Evaluation(
        dataset=examples, scorers=[match_score1], evaluation_name="My Evaluation"
    )
    ```

    #### 개별 평가 run 이름 지정

    특정 평가 run(`evaluate()` 호출)의 이름을 지정하려면 `display_name`이 포함된 `__weave` 딕셔너리를 사용하세요. 그러면 해당 run에 대해 UI에 표시되는 이름이 달라집니다.

    ```python lines theme={null}
    evaluation = Evaluation(
        dataset=examples, scorers=[match_score1]
    )
    evaluation.evaluate(model, __weave={"display_name": "My Evaluation Run"})
    ```
  </Tab>

  <Tab title="TypeScript">
    `Evaluation` 객체의 이름을 지정하려면 `Evaluation` 생성자에 `id` 매개변수를 전달하세요. 이 이름은 코드와 UI 목록에서 Evaluation을 파악하는 데 도움이 됩니다.

    ```typescript lines theme={null}
    const evaluation = new weave.Evaluation({
      id: 'my-evaluation',
      dataset: dataset,
      scorers: [matchScore],
    });
    ```
  </Tab>
</Tabs>

<div id="2-define-a-dataset-of-test-examples">
  ## 2. 테스트 예제용 데이터셋 정의
</div>

먼저, 평가할 예제 모음이 포함된 [Dataset](../core-types/datasets) 객체 또는 예제 목록을 정의합니다. 이러한 예제는 보통 테스트하려는 실패 케이스이며, 테스트 주도 개발(TDD)의 단위 테스트와 비슷합니다.

<Tabs>
  <Tab title="Python">
    다음 예제는 딕셔너리 목록으로 정의한 데이터셋을 보여줍니다:

    ```python lines theme={null}
    examples = [
        {"question": "What is the capital of France?", "expected": "Paris"},
        {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
        {"question": "What is the square root of 64?", "expected": "8"},
    ]
    ```
  </Tab>

  <Tab title="TypeScript">
    다음 예제는 row 배열을 포함한 `Dataset` 객체로 정의한 데이터셋을 보여줍니다:

    ```typescript lines theme={null}
    const dataset = new weave.Dataset({
      id: 'my-dataset',
      rows: [
        {question: 'What is the capital of France?', expected: 'Paris'},
        {question: 'Who wrote "To Kill a Mockingbird"?', expected: 'Harper Lee'},
        {question: 'What is the square root of 64?', expected: '8'},
      ],
    });
    ```
  </Tab>
</Tabs>

<div id="3-define-scoring-functions">
  ## 3. 채점 함수를 정의합니다
</div>

그런 다음 하나 이상의 [채점 함수](../evaluation/scorers)를 만듭니다. 이 함수들은 `Dataset`의 각 예제에 점수를 매기는 데 사용됩니다.

<Tabs>
  <Tab title="Python">
    각 채점 함수에는 `output` 매개변수가 있어야 하며, 점수를 담은 딕셔너리를 반환해야 합니다. 필요하다면 예제의 다른 입력도 포함할 수 있습니다.

    채점 함수에는 `output` 키워드 인자가 필요하지만, 나머지 인자는 사용자가 정의하며 데이터셋 예제에서 가져옵니다. 인자 이름을 기준으로 딕셔너리 키를 매칭해 필요한 키만 사용합니다.

    <Tip>
      scorer가 `output` 인자를 기대하는데 전달받지 못한다면, 레거시 `model_output` 키를 사용하고 있는지 확인하세요. 이를 해결하려면 scorer 함수에서 `output`을 키워드 인수로 사용하도록 업데이트하세요.
    </Tip>

    다음 예제 scorer 함수 `match_score1`는 채점에 `examples` 딕셔너리의 `expected` 값을 사용합니다.

    ```python lines theme={null}
    import weave

    # 예제를 수집합니다
    examples = [
        {"question": "What is the capital of France?", "expected": "Paris"},
        {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
        {"question": "What is the square root of 64?", "expected": "8"},
    ]

    # 맞춤형 채점 함수를 정의합니다
    @weave.op()
    def match_score1(expected: str, output: dict) -> dict:
        # 여기에서 모델 출력에 점수를 매기는 로직을 정의합니다
        return {'match': expected == output['generated_text']}
    ```

    ### (선택) 맞춤형 `Scorer` 클래스를 정의합니다

    일부 애플리케이션에서는 맞춤형 `Scorer` 클래스를 만들고 싶을 수 있습니다. 예를 들어 특정 매개변수(예: chat model, prompt), 각 행에 대한 특정 채점 방식, 그리고 집계 점수의 특정 계산 방식을 갖춘 표준화된 `LLMJudge` 클래스를 만들 수 있습니다.

    자세한 내용은 [RAG 애플리케이션의 모델 기반 Evaluation](/ko/weave/tutorial-rag#optional-defining-a-scorer-class)에서 `Scorer` 클래스를 정의하는 튜토리얼을 참조하세요.
  </Tab>

  <Tab title="TypeScript">
    각 채점 함수는 `weave.op`로 래핑되며, `modelOutput` 및 `datasetRow` 속성이 있는 객체를 받습니다.

    다음 예제 scorer 함수 `matchScore`는 모델 출력을 데이터셋 행의 `expected` 값과 비교합니다.

    ```typescript lines theme={null}
    import * as weave from "weave"

    // 예제를 데이터셋으로 수집합니다
    const dataset = new weave.Dataset({
      id: "my-dataset",
      rows: [
        { question: "What is the capital of France?", expected: "Paris" },
        { question: 'Who wrote "To Kill a Mockingbird"?', expected: "Harper Lee" },
        { question: "What is the square root of 64?", expected: "8" }
      ]
    })

    // 맞춤형 채점 함수를 정의합니다
    const matchScore = weave.op(
      ({ modelOutput, datasetRow }) => {
        return { match: modelOutput === datasetRow.expected }
      },
      { name: "matchScore" }
    )
    ```

    <Note>
      클래스 기반 `Scorer` 유형은 아직 TypeScript에서 사용할 수 없습니다. `weave.op`로 래핑한 함수 기반 scorer를 사용하세요.
    </Note>
  </Tab>
</Tabs>

<div id="4-define-a-model-or-function-to-evaluate">
  ## 4. 평가할 모델 또는 함수 정의하기
</div>

<Tabs>
  <Tab title="Python">
    `Model`을 평가하려면 `Evaluation`으로 `.evaluate()`를 호출하세요. `Models`는 실험해 보고 Weave에서 캡처하려는 매개변수가 있을 때 사용합니다.

    ```python lines theme={null}
    from weave import Model, Evaluation
    import asyncio

    class MyModel(Model):
        prompt: str

        @weave.op()
        def predict(self, question: str):
            # 여기에 LLM 호출을 추가하고 출력을 반환합니다
            return {'generated_text': 'Hello, ' + self.prompt}

    model = MyModel(prompt='World')

    evaluation = Evaluation(
        dataset=examples, scorers=[match_score1]
    )
    weave.init('intro-example') # Weave로 결과 추적 시작
    asyncio.run(evaluation.evaluate(model))
    ```

    이 코드는 각 예제에 대해 `predict`를 실행하고, 각 채점 함수로 출력에 점수를 매깁니다.

    ### (선택 사항) 평가할 함수 정의하기

    또는 `@weave.op()`로 추적되는 맞춤형 함수를 평가할 수도 있습니다.

    ```python lines theme={null}
    @weave.op
    def function_to_evaluate(question: str):
        # 여기에 LLM 호출을 추가하고 출력을 반환합니다
        return  {'generated_text': 'some response'}

    asyncio.run(evaluation.evaluate(function_to_evaluate))
    ```
  </Tab>

  <Tab title="TypeScript">
    TypeScript에서는 `weave.op`으로 래핑한 함수를 평가합니다. 이 함수는 데이터셋 행을 받아 모델 출력을 반환합니다.

    ```typescript lines theme={null}
    import * as weave from "weave"

    // Weave 초기화
    await weave.init("intro-example")

    // 평가할 함수 정의
    const myModel = weave.op(
      async ({ question }) => {
        // 여기에 LLM 호출을 추가하고 출력을 반환합니다
        return "Paris"
      },
      { name: "myModel" }
    )

    // 평가 생성
    const evaluation = new weave.Evaluation({
      id: "my-evaluation",
      dataset: dataset,
      scorers: [matchScore]
    })

    // 평가 실행
    const results = await evaluation.evaluate({ model: myModel })
    ```

    이 코드는 각 예제에 대해 `myModel`을 실행하고, 각 채점 함수로 출력에 점수를 매깁니다.
  </Tab>
</Tabs>

<div id="5-run-the-evaluation">
  ## 5. 평가 실행
</div>

평가를 실행하려면 `Evaluation` 객체에서 `.evaluate()`를 호출합니다.

<Tabs>
  <Tab title="Python">
    `evaluation`이라는 `Evaluation` 객체와 평가할 `model`이라는 `Model` 객체가 있다고 가정하면, 다음 코드는 평가 run을 생성합니다.

    ```python lines theme={null}
    asyncio.run(evaluation.evaluate(model))
    ```

    ### (선택) 여러 번 실행

    각 예제를 여러 번 실행하려면 `Evaluation` 객체에서 `trials` 파라미터를 설정할 수 있습니다.

    ```python lines theme={null}
    evaluation = Evaluation(
        dataset=examples,
        scorers=[match_score],
        trials=3
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    `evaluation`이라는 `Evaluation` 객체와 `myModel`이라는 모델 함수가 있다고 가정하면, 다음 코드는 평가를 실행합니다.

    ```typescript lines theme={null}
    const results = await evaluation.evaluate({model: myModel});
    ```

    ### (선택) 여러 번 실행

    각 예제를 여러 번 실행하려면 `evaluate()`를 호출할 때 `nTrials` 파라미터를 설정할 수 있습니다.

    ```typescript lines theme={null}
    const results = await evaluation.evaluate({
      model: myModel,
      nTrials: 3,
    });
    ```
  </Tab>
</Tabs>

이 run에서는 각 예제가 모델에 세 번 전달되며, 각 run은 독립적으로 점수가 매겨지고 Weave에 개별적으로 표시됩니다.

<div id="full-evaluation-code-example">
  ## 전체 평가 코드 예시
</div>

<Tabs>
  <Tab title="Python">
    다음 코드 샘플은 처음부터 끝까지 전체 평가 run 과정을 보여줍니다. `examples` 딕셔너리는 `prompt` 값이 주어졌을 때 `MyModel`을 평가하는 데 `match_score1` 및 `match_score2` Scorer 함수에서 사용되며, 맞춤형 함수 `function_to_evaluate`를 평가할 때도 사용됩니다. `Model`과 함수에 대한 평가는 모두 `asyncio.run(evaluation.evaluate())`로 호출합니다.

    ```python lines theme={null}
    from weave import Evaluation, Model
    import weave
    import asyncio
    weave.init('intro-example')
    examples = [
        {"question": "What is the capital of France?", "expected": "Paris"},
        {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
        {"question": "What is the square root of 64?", "expected": "8"},
    ]

    @weave.op()
    def match_score1(expected: str, output: dict) -> dict:
        return {'match': expected == output['generated_text']}

    @weave.op()
    def match_score2(expected: dict, output: dict) -> dict:
        return {'match': expected == output['generated_text']}

    class MyModel(Model):
        prompt: str

        @weave.op()
        def predict(self, question: str):
            # 여기에서 LLM 호출을 추가하고 출력을 반환합니다
            return {'generated_text': 'Hello, ' + question + self.prompt}

    model = MyModel(prompt='World')
    evaluation = Evaluation(dataset=examples, scorers=[match_score1, match_score2])

    asyncio.run(evaluation.evaluate(model))

    @weave.op()
    def function_to_evaluate(question: str):
        # 여기에서 LLM 호출을 추가하고 출력을 반환합니다
        return  {'generated_text': 'some response' + question}

    asyncio.run(evaluation.evaluate(function_to_evaluate("What is the capitol of France?")))
    ```
  </Tab>

  <Tab title="TypeScript">
    다음 코드 샘플은 처음부터 끝까지 전체 평가 run 과정을 보여줍니다. 데이터셋 행은 `myModel`을 평가하는 데 `matchScore` Scorer 함수에서 사용됩니다.

    ```typescript lines theme={null}
    import * as weave from "weave"

    // Weave를 초기화합니다
    await weave.init("intro-example")

    // 예제를 데이터셋으로 모읍니다
    const dataset = new weave.Dataset({
      id: "my-dataset",
      rows: [
        { question: "What is the capital of France?", expected: "Paris" },
        { question: 'Who wrote "To Kill a Mockingbird"?', expected: "Harper Lee" },
        { question: "What is the square root of 64?", expected: "8" }
      ]
    })

    // Scorer 함수를 정의합니다
    const matchScore = weave.op(
      ({ modelOutput, datasetRow }) => {
        return { match: modelOutput === datasetRow.expected }
      },
      { name: "matchScore" }
    )

    // 평가할 함수를 정의합니다
    const myModel = weave.op(
      async ({ question }) => {
        // 여기에서 LLM 호출을 추가하고 출력을 반환합니다
        return "Paris"
      },
      { name: "myModel" }
    )

    // 평가를 생성하고 실행합니다
    const evaluation = new weave.Evaluation({
      id: "my-evaluation",
      dataset: dataset,
      scorers: [matchScore]
    })

    const results = await evaluation.evaluate({ model: myModel })
    console.log("Evaluation results:", results)
    ```
  </Tab>
</Tabs>

<Frame>
  <img src="https://mintcdn.com/wb-21fd5541-docs-hivemind-launch/GExaOHOKeIAPE2Yp/images/evals-hero.png?fit=max&auto=format&n=GExaOHOKeIAPE2Yp&q=85&s=6f50fbb215d35899b6df46aeb35ed360" alt="Evals hero" width="4100" height="2160" data-path="images/evals-hero.png" />
</Frame>

<div id="advanced-evaluation-usage">
  ## 평가 고급 사용법
</div>

<div id="format-dataset-rows-before-evaluating">
  ### 평가 전에 데이터셋 행 형식 맞추기
</div>

<Tabs>
  <Tab title="Python">
    <Warning>
      `preprocess_model_input` 함수는 입력이 모델의 예측 함수에 전달되기 전에만 적용됩니다. scorer 함수는 전처리가 적용되지 않은 원본 데이터셋 예제를 항상 전달받습니다.
    </Warning>

    `preprocess_model_input` 매개변수를 사용하면 데이터셋 예제가 평가 함수에 전달되기 전에 변환할 수 있습니다. 다음과 같은 경우에 유용합니다.

    * 모델이 기대하는 입력에 맞게 필드 이름 바꾸기
    * 데이터를 올바른 형식으로 변환하기
    * 필드 추가 또는 제거하기
    * 각 예제에 대해 추가 데이터 불러오기

    다음은 `preprocess_model_input`를 사용해 필드 이름을 바꾸는 방법을 보여주는 간단한 예제입니다.

    ```python lines theme={null}
    import weave
    from weave import Evaluation
    import asyncio

    # 데이터셋에는 "input_text"가 있지만 모델은 "question"을 기대합니다
    examples = [
        {"input_text": "What is the capital of France?", "expected": "Paris"},
        {"input_text": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
        {"input_text": "What is the square root of 64?", "expected": "8"},
    ]

    @weave.op()
    def preprocess_example(example):
        # input_text를 question으로 이름 변경
        return {
            "question": example["input_text"]
        }

    @weave.op()
    def match_score(expected: str, output: dict) -> dict:
        return {'match': expected == output['generated_text']}

    @weave.op()
    def function_to_evaluate(question: str):
        return {'generated_text': f'Answer to: {question}'}

    # 전처리를 포함한 평가 생성
    evaluation = Evaluation(
        dataset=examples,
        scorers=[match_score],
        preprocess_model_input=preprocess_example
    )

    # 평가 실행
    weave.init('preprocessing-example')
    asyncio.run(evaluation.evaluate(function_to_evaluate))
    ```

    이 예제에서는 데이터셋에 `input_text` 필드가 있는 예제가 들어 있지만, 평가 함수는 `question` 매개변수를 기대합니다. `preprocess_example` 함수는 필드 이름을 바꿔 각 예제를 변환하므로 평가가 올바르게 작동할 수 있습니다.

    전처리 함수는 다음과 같이 동작합니다.

    1. 데이터셋에서 원본 예제를 받습니다
    2. 모델이 기대하는 필드가 포함된 딕셔너리를 반환합니다
    3. 각 예제가 평가 함수에 전달되기 전에 적용됩니다

    이는 모델이 기대하는 필드 이름이나 구조와 다른 외부 데이터셋으로 작업할 때 특히 유용합니다.
  </Tab>

  <Tab title="TypeScript">
    TypeScript에서는 `Evaluation` 객체의 `columnMapping`을 사용해 데이터셋 컬럼 이름을 scorer가 기대하는 이름에 매핑할 수 있습니다. 데이터셋의 필드 이름이 scorer 함수가 기대하는 이름과 다를 때 유용합니다.

    다음 예제는 `expectedOutputTimesTwo` 컬럼을 `expected` 컬럼에 매핑합니다.

    ```typescript lines theme={null}
    const myScorer = weave.op(
      ({ modelOutput, datasetRow }) => {
        return modelOutput * 2 === datasetRow.expectedOutputTimesTwo
      },
      { name: "myScorer" }
    )

    const evaluation = new weave.Evaluation({
      id: "my-evaluation",
      dataset: [{ expected: 2 }],
      scorers: [myScorer],
      columnMapping: { expectedOutputTimesTwo: "expected" }
    })
    ```

    <Note>
      `preprocess_model_input` 매개변수는 아직 TypeScript에서 사용할 수 없습니다. 데이터셋 필드를 scorer가 기대하는 형식에 맞추려면 `columnMapping`을 사용하세요.
    </Note>
  </Tab>
</Tabs>

<div id="use-huggingface-datasets-with-evaluations">
  ### 평가에서 HuggingFace 데이터셋 사용하기
</div>

<Tabs>
  <Tab title="Python">
    서드파티 서비스 및 라이브러리와의 인테그레이션을 지속적으로 개선하고 있습니다.

    더 원활한 인테그레이션을 제공하기 위해 작업하는 동안, 현재는 Weave 평가에서 HuggingFace Datasets를 사용하기 위한 임시 우회 방법으로 `preprocess_model_input`을 사용할 수 있습니다.

    현재 방법은 [평가에서 HuggingFace 데이터셋 사용 cookbook](/ko/weave/cookbooks/hf_dataset_evals)을 참조하세요.
  </Tab>

  <Tab title="TypeScript">
    ```plaintext theme={null}
    이 기능은 현재 TypeScript에서 사용할 수 없습니다.
    ```
  </Tab>
</Tabs>

<div id="saved-views">
  ### 저장된 뷰
</div>

Evals 테이블의 설정, 필터, 정렬을 \_저장된 뷰\_로 저장해 선호하는 설정에 빠르게 접근할 수 있습니다. 저장된 뷰는 UI와 Python SDK에서 설정하고 사용할 수 있습니다. 자세한 내용은 [저장된 뷰](/ko/weave/guides/tools/saved-views)를 참조하세요.

<div id="imperative-evaluations-evaluationlogger">
  ### 명령형 평가 (`EvaluationLogger`)
</div>

더 유연한 평가 프레임워크를 선호한다면 Weave의 [`EvaluationLogger`](../evaluation/evaluation_logger)를 확인해 보세요. `EvaluationLogger`는 Python과 TypeScript 모두에서 사용할 수 있으며, 복잡한 워크플로에 더 유연하게 대응할 수 있습니다. 반면 표준 평가 프레임워크는 더 체계적인 구조와 가이드를 제공합니다.
