Github Copilot을 Reverse Engineering하는 글을 공부하면서 번역/의역, 추가조사한 내용을 정리한 글입니다.

코파일럿이란 무엇인가요?

Copilot으로 이런 일을 해볼 수 있습니다.

  1. GitHub Copilot은 실시간 코드 제안을 제공합니다. 여기에는 프로그래머의 작업 컨텍스트에 따라 코드 스니펫, 함수, 전체 파일까지 포함됩니다.
  2. GitHub Copilot은 기본 Codex 모델과 활용되는 컨텍스트 정보 덕분에 다른 파일을 포함하여 코드베이스의 주변 코드에서 함수/변수를 추측할 수 있습니다.

어떻게 Copilot은 실시간으로 내 코드베이스에 적합한 코드를 추천할 수 있을까?

  1. Github Copilot 구조
    1. 깃허브 코파일럿은 프로그래머와 상호작용하는 VSCode 확장 프로그램인 클라이언트와 프롬프트를 처리하고 제안을 반환하는 OpenAI의 코덱스와 유사한 모델이라는 두 가지 주요 구성 요소로 이루어져 있습니다.

      Github Copilot의 주요 구성 요소는 크게 두 가지입니다:

      1. 클라이언트: VSCode 확장 프로그램은 사용자가 입력하는 모든 것('프롬프트'라고 함)을 수집하여 코덱스와 같은 모델에 보냅니다. 모델이 반환하는 내용이 무엇이든 에디터에 표시됩니다.
      2. 모델: 코덱스와 유사한 모델이 프롬프트를 받아 프롬프트를 완성하는 제안을 반환합니다.

비밀 소스 1: 프롬프트 엔지니어링

  1. 기본 용어 정의

    1. '접두사' 키는 모델이 작업할 컨텍스트를 보유합니다. 이 컨텍스트는 코드, 주석 또는 모델에 완성을 생성하는 데 필요한 배경을 제공하는 모든 텍스트가 될 수 있습니다.
    2. "접미사" 키는 원하는 완성 뒤에 오는 추가 컨텍스트를 제공합니다. 기본적으로 완성에 대한 사후 컨텍스트를 설정하므로 더 큰 코드베이스 내의 특정 위치에 맞아야 하는 코드를 생성하는 데 유용할 수 있습니다. "접미사"가 있으면 코파일럿이 "삽입 모드" 또는 "중간 채우기"(FIM) 모드를 사용하고 있음을 나타냅니다.
    3. "isFimEnabled" 키는 FIM 모드가 활성화되었는지 여부를 나타내는 부울입니다. 참이면 모델에 접두사와 접미사 사이에 맞는 코드를 생성하라는 메시지가 표시됩니다.
    4. "promptElementRanges" 키는 전체 문자열에서 문자 위치 측면에서 프롬프트의 여러 부분의 범위에 대한 정보를 제공합니다. 이는 프롬프트에 포함된 코드 및 주석의 다른 부분을 참조할 수 있습니다.
    5. Fill-in-Middle (FIM) 모드를 사용하면 모델이 기존 코드의 특정 부분에 맞는 코드를 생성할 수 있습니다. 이 모드는 사용자가 제공한 접두사와 접미사를 고려하여 다음과 같은 컨텍스트에 적합한 코드를 생성합니다.
      • 모델에 접두사와 접미사를 모두 제공합니다. 접두사는 모델에서 생성하려는 코드 앞에 오는 코드 부분이고 접미사는 뒤에 오는 코드 부분입니다.
      • 접두사와 접미사를 모두 사용하면 기본적으로 모델에 앞뒤에 오는 내용을 고려하여 의미 있는 코드를 생성하도록 요청하는 것입니다(따라서 "중간 채우기").
      • 예제에서 '접두사'에는 프로젝트의 다른 파일에 있는 일부 코드가 포함되어 있어 AI 모델에 컨텍스트를 제공합니다.
  2. Prompt는 어떻게 생겼는가?

    {
      "prefix": "# Path: codeviz\\app.py\n# Compare this snippet from codeviz\\predictions.py:\n# import json\n# import sys\n# import time\n# from manifest import Manifest\n# \n# sys.path.append(__file__ + \"/..\")\n# from common import module_codes, module_deps, module_categories, data_dir, cur_dir\n# \n# gold_annots = json.loads(open(data_dir / \"gold_annotations.js\").read().replace(\"let gold_annotations = \", \"\"))\n# \n# M = Manifest(\n#     client_name = \"openai\",\n#     client_connection = open(cur_dir / \".openai-api-key\").read().strip(),\n#     cache_name = \"sqlite\",\n#     cache_connection = \"codeviz_openai_cache.db\",\n#     engine = \"code-davinci-002\",\n# )\n# \n# def predict_with_retries(*args, **kwargs):\n#     for _ in range(5):\n#         try:\n#             return M.run(*args, **kwargs)\n#         except Exception as e:\n#             if \"too many requests\" in str(e).lower():\n#                 print(\"Too many requests, waiting 30 seconds...\")\n#                 time.sleep(30)\n#                 continue\n#             else:\n#                 raise e\n#     raise Exception(\"Too many retries\")\n# \n# def collect_module_prediction_context(module_id):\n#     module_exports = module_deps[module_id][\"exports\"]\n#     module_exports = [m for m in module_exports if m != \"default\" and \"complex-export\" not in m]\n#     if len(module_exports) == 0:\n#         module_exports = \"\"\n#     else:\n#         module_exports = \"It exports the following symbols: \" + \", \".join(module_exports)\n#     \n#     # get module snippet\n#     module_code_snippet = module_codes[module_id]\n#     # snip to first 50 lines:\n#     module_code_snippet = module_code_snippet.split(\"\\n\")\n#     if len(module_code_snippet) > 50:\n#         module_code_snippet = \"\\n\".join(module_code_snippet[:50]) + \"\\n...\"\n#     else:\n#         module_code_snippet = \"\\n\".join(module_code_snippet)\n#     \n#     return {\"exports\": module_exports, \"snippet\": module_code_snippet}\n# \n# #### Name prediction ####\n# \n# def _get_prompt_for_module_name_prediction(module_id):\n#     context = collect_module_prediction_context(module_id)\n#     module_exports = context[\"exports\"]\n#     module_code_snippet = context[\"snippet\"]\n# \n#     prompt = f\"\"\"\\\n# Consider the code snippet of an unmodule named.\n# \nimport json\nfrom flask import Flask, render_template, request, send_from_directory\nfrom common import *\nfrom predictions import predict_snippet_description, predict_module_name\n\napp = Flask(__name__)\n\[email protected]('/')\ndef home():\n    return render_template('code-viz.html')\n\[email protected]('/data/<path:filename>')\ndef get_data_files(filename):\n    return send_from_directory(data_dir, filename)\n\[email protected]('/api/describe_snippet', methods=['POST'])\ndef describe_snippet():\n    module_id = request.json['module_id']\n    module_name = request.json['module_name']\n    snippet = request.json['snippet']\n    description = predict_snippet_description(\n        module_id,\n        module_name,\n        snippet,\n    )\n    return json.dumps({'description': description})\n\n# predict name of a module given its id\[email protected]('/api/predict_module_name', methods=['POST'])\ndef suggest_module_name():\n    module_id = request.json['module_id']\n    module_name = predict_module_name(module_id)\n",
      "suffix": "if __name__ == '__main__':\r\n    app.run(debug=True)",
      "isFimEnabled": true,
      "promptElementRanges": [
        { "kind": "PathMarker", "start": 0, "end": 23 },
        { "kind": "SimilarFile", "start": 23, "end": 2219 },
        { "kind": "BeforeCursor", "start": 2219, "end": 3142 }
      ]
    }