AI-Assisted Programming 온라인 세미나3 – 3장. 프롬프트 엔지니어링

프롬프트 엔지니어링은 LLM에게 원하는 답을 얻기 위한 방법을 찾습니다.

이렇게 생각해 보세요: 누군가에게 조언을 구할 때 상황 설명을 하고 필요한 것을 명확히 해야 하는 것처럼, LLM과의 소통도 마찬가지입니다. 질문이나 프롬프트를 신중하게 작성해야 합니다. 때로는 LLM이 당신의 질문을 정확히 이해할 수 있도록 힌트나 추가 정보를 질문에 포함시켜야 할 수도 있습니다.

이는 단순히 일회성 질문을 하는 것에 그치지 않습니다. 때로는 LLM과 전체 대화를 나누는 것과 같아서, 필요한 정보를 얻을 때까지 질문을 주고받으며 조정해 나가는 과정입니다.

 

과정이니 챗 방식으로 대화를 하는 것은 자연스럽습니다. 원하는 것이 있는 사람이 대화를 이끌어 가야 합니다. 아직 LLM이 어떤 방식으로 일 처리하는지가 분명하지 않기 때문에,  프롬프트 엔지니어링이란 분야로 연구될 정도로 이 대화를 이끌어 가는 것이 쉽지 않습니다.

뭔가를 원하니까 AI를 사용하려고 겠죠? 뭘 원할까요? 궁금한게 있거나 뭐 시킬 일이 있겠죠. 생성형 AI가 잘 하는 것이 생성이니 뭔가 생성하라고(만들라고) 할 게 있다는 것입니다. 궁금한 것을 물으면 LLM은 응답을 해야 하니, 응답을 생성해야 합니다.

생성형 AI는 아무렇게 생성하지 않습니다. 모델에 따릅니다. 원하는 것을 생성하게 하려면 모델이 어떻게 요구에 응답하는 지를 알아야 합니다. 우리는 아직까지 생성형 AI 모델이 어떻게 생성 지시를 따르는지 잘 모릅니다. 그래서 경험해 봐야 합니다. 이런 이유로 프롬프트 엔지니어링은 경험을 중시하는 엔지니어링입니다.

 

재미나 흥미를 위한 것이 아니라, 원하는 응답을 얻으려 한다면 LLM의 특징을 잘 알아야 합니다. 그래야 LLM이 잘못하는 것을 보완하도록 대화를 이끌어 갈 수 있습니다.

 

잘 알아둬야 할 LLM의 특징들

  • “간결하게”란 말은 기억해 둘 필요가 있습니다.
    • LLM은 답변을 하는 게 목표이다 보니 맞지 않는 이야기도 맞는 것처럼 하고, 할 수  있는 한 많은 내용을 포함하려고 합니다.
  • 모델마다 훈련 데이터와 방법이 달라 LLM이라 해도 동일한 프롬프트에 대해 다르게 답할 수 있습니다.
  • 길게 많은 말을 한다고 지시를 잘 따르는 것은 아닙니다.
  • 명확해야 합니다. 지시가 불 분명하면 뭘 잘해야 할 지 알 수 없습니다. 이건 너무 당연하지만 대충 이야기해도 잘 알아듣는 것처럼 응답할 때를 자주 경험하다보면, 프롬프트를 잘 작성하는 것은 수고가 따르기 때문에 대충 말하는 게 습관이 될 수 있습니다. 조심해야 합니다.
  • 프롬프트가 불분명하면 LLM이 혼란스러워하고 완전히 빗나가거나 순전히 허구인 응답을 제공할 수 있습니다. 명확성이 핵심입니다.

 

프롬프트 네 가지 주요 구성 요소
  • 맥락
    • 프롬프트를 시작할 때 종종 맥락을 제공하는 한두 문장으로 시작합니다. 주로 AI가 응답을 제공할 때 취해야 할 역할이나 페르소나를 지정합니다. 이는 더 정확할 뿐만 아니라 맥락적으로 관련성 있는 응답으로 이어져 더 의미 있는 결과를 보장합니다.
  • 지시사항
    • 요약, 번역 또는 분류와 같이 LLM이 해야 하는 일 입니다.
    • 프롬프트에는 최소한 하나의 명확한 지시사항이 포함되어야 합니다. 여러 지시사항이 있으면 상황이 약간 모호해질 수 있습니다. 명확하지 않거나 서로 충돌하는 것처럼 보이면 LLM이 어느 것에 집중해야 할지 또는 모든 것을 어떻게 균형 있게 처리해야 할지 혼란스러워할 수 있습니다. 더 많은 지시사항은 LLM이 처리해야 할 것이 더 많다는 의미입니다. 프롬프트의 각 부분을 처리하고 이해한 다음, 모든 부분을 일관된 응답으로 어떻게 엮을지 파악해야 합니다. 이는 많은 정신적 노력이 필요하며, 때로는 실수나 부적절한 답변으로 이어질 수 있습니다.
    • 그리고 잊지 마세요, LLM은 지시사항을 순서대로 하나씩 처리합니다. 따라서 이러한 쿼리를 배열하는 방식이 해석 방법과 얻게 되는 답변의 종류에 영향을 미칠 수 있습니다. 이를 고려할 때, 전문가의 조언은 간단하게 유지하는 것입니다. LLM에 한꺼번에 전체 질문 목록을 던지는 대신, 일련의 작은 프롬프트로 나누어 보세요. 이는 독백을 전달하는 대신 대화를 나누는 것과 같습니다.
  • 입력
    • 처리해야 할 구체적인 대상
    • 내용 입력
      프롬프트를 작성할 때는 ###나 “””와 같은 특수 기호를 사용하여 지시사항과 LLM이 작업할 내용이나 정보를 명확히 구분하는 것이 도움이 됩니다. 이러한 기호들은 경계나 표시 역할을 하여 지시사항이 어디서 끝나고 내용이 어디서 시작되는지 분명히 합니다.소프트웨어 개발자가 통합하려는 새로운 API에 관한 긴 문서에서 핵심 내용을 요약하는 데 도움이 필요한 시나리오를 고려해 봅시다. 다음과 같이 프롬프트를 구성할 수 있습니다:프롬프트: 아래 텍스트에서 API 구현을 위한 주요 단계를 추출하세요:
      문서: “””
      {API 문서 텍스트}
      “””””” 구분 기호를 사용하는 것은 지시사항과 API 문서 텍스트를 분리하는 깔끔한 방법입니다. 이는 LLM에게 무엇을 해야 하는지 더 명확한 그림을 제공하고, 주요 단계에 대한 간결한 요약을 얻을 가능성을 높입니다. 또한 이러한 구분 기호는 프롬프트를 정리하여 읽기 쉽게 만들어, 더 길거나 복잡한 텍스트 입력에 대해 정말 유용합니다.
  • 출력 형식
    • LLM이 지시 수행 결과를 제공해야 하는 형식. 생성형 AI는 생성을 특징으로 하는 LLM입니다. 언어 모델이니 주로 텍스트를 생성하지만, 오디오, 이미지, 비디오 형식도 지원합니다. 생성 결과에 원하는 형식이 있다는 것은 그것을 다른 곳에 사용하려는 이유가 있어서 입니다. 대부분의 프로그래밍 환경에서 지원하는 JSON 형태가 대표적인 출력 형식입니다.
    • 표 “Python, Java, C++의 문법, 성능, 사용 사례를 비교하는 표를 만드세요.”
    • 목록 “웹 페이지 로딩이 느릴 때 문제를 해결하는 단계를 나열하세요.”
    • 마크다운/HTML “GET과 POST HTTP 메서드의 차이점을 마크다운으로 설명하세요.”
    • 텍스트 계층 구조 “소프트웨어 개발 생명 주기(SDLC)의 구조화된 개요를 제공하세요. 각 단계와 주요 활동을 포함해야 합니다.”
    • LaTeX 포맷 “이진 검색 알고리즘의 시간 복잡도를 LaTeX 표기법으로 표현하세요.”
    •  프롬프트를 통해 응답의 길이도 지정할 수 있습니다.
      • “간단한 요약을 제공하세요” 또는 “상세한 설명을 작성하세요”와 같은 지시를 통해 LLM을 안내할 수 있습니다.
      • 응답이 300단어를 넘지 않아야 한다고 말하는 등 더 구체적으로 지정할 수 있습니다. LLM이 제공한 단어 제한을 초과할 수 있지만, 적어도 일반적인 범위 내에 있을 것입니다.

 

Best Practices

다음으로 원하는 답변을 얻는 데 도움이 되는 프롬프트를 만드는 몇 가지 모범 사례를 살펴보겠습니다. 하지만 이를 절대적인 규칙으로 받아들이지 마세요. 이러한 제안은 엄격한 규칙이라기보다는 다소 주관적일 수 있는 일반적인 조언에 가깝습니다. LLM과 더 많은 시간을 대화하다 보면, 아마도 여러분에게 맞는 질문 방식을 우연히 발견하게 될 것입니다. 이는 모두 프롬프트 엔지니어링 여정의 일부입니다.

  • 구체적으로 작성하세요
    적절한 프롬프트를 만드는 것은 좋은 대화에서 적절한 지점을 찾는 것과 같으며, 이는 이러한 텍스트 생성 시스템과 잘 어울리기 위한 가장 중요한 단계일 수 있습니다. 더 많은 세부 사항을 제공할수록 좋습니다. 또한 명확해야 합니다. 그렇지 않으면 LLM이 가정을 하거나 심지어 환각을 일으킬 수 있습니다.

    • 프롬프트: 문자열에서 날짜를 파싱하는 Python 함수를 개발하세요. 이 함수는 YYYY-MM-DD, MM/DD/YYYY, Month DD, YYYY 형식을 처리할 수 있어야 합니다. 함수는 datetime 객체를 반환해야 합니다. 각 형식의 예시를 최소 세 개씩 올바르게 처리하는 것을 보여주는 스크립트를 제공하고, 의존성, 함수에 사용된 로직, 스크립트 실행 방법에 대한 설명을 포함한 문서를 함께 제공하세요.
    • 프롬프트: 2023년 4분기에 500달러 이상 구매한 고객 목록을 우리 데이터베이스에서 검색하는 SQL 쿼리를 개발하세요. 쿼리는 고객의 전체 이름, 이메일 주소, 총 지출 금액, 마지막 구매 날짜를 반환해야 합니다. 결과는 총 지출 금액을 기준으로 내림차순으로 정렬되어야 합니다. 쿼리가 성능에 최적화되도록 해주세요.
  • 두문자어와 전문 용어
    • 프롬프트를 작성할 때 전문 용어와 두문자어를 명확히 하는 것이 중요합니다. 이러한 전문 용어는 맥락에 따라 다른 의미를 가질 수 있으며, 도움이 되지 않는 응답을 초래할 수 있습니다. 따라서 두문자어를 풀어 쓰고 사용된 전문 용어에 대해 명확한 정의나 설명을 제공하는 것이 좋습니다.
    • 예를 들어, ChatGPT를 사용하여 데이터베이스 연결 문제를 해결하려고 한다고 가정해 봅시다. 잘못 작성된 프롬프트는 다음과 같을 수 있습니다:
    • 프롬프트: DB 연결 문제가 있습니다. 어떻게 해결하나요?
      • 이 프롬프트에서 “DB”는 MySQL, PostgreSQL 또는 다른 데이터베이스 시스템을 지칭할 수 있어 모호하며, 연결 문제의 성격도 명확하지 않습니다.
      • 더 효과적인 프롬프트는 다음과 같을 것입니다: 프롬프트: JDBC를 사용하여 PostgreSQL 데이터베이스에 연결하려고 할 때 연결 시간 초과 문제가 발생하고 있습니다. 이를 어떻게 해결할 수 있을까요? 이 프롬프트는 사용 중인 데이터베이스 시스템, 연결 방법, 그리고 발생한 구체적인 문제를 명확히 설명하고 있습니다.
      • 마크 트웨인은 한때 이렇게 썼습니다. “거의 맞는 단어와 정확한 단어의 차이는 실로 큰 문제입니다. 그것은 반딧불과 번개의 차이와 같습니다.” 어떤 면에서 프롬프트를 작성하는 것에 대해서도 같은 말을 할 수 있습니다.
  • 제로샷 및 퓨샷 학습
    • 제로샷 학습에서는 하나의 프롬프트를 제공하고 원하는 답변을 얻습니다. 대개 이 방식으로 충분합니다. 하지만 프로그래밍 언어와 프레임워크의 복잡성을 고려할 때, 때로는 LLM을 조금 더 유도해야 할 필요가 있습니다. 이는 퓨샷 학습으로 할 수 있습니다. 퓨샷 학습은 LLM이 매우 적은 예시나 훈련 데이터만으로도 과제를 이해하고 수행할 수 있는 능력을 말합니다. 이는 대규모의 훈련 데이터가 필요한 전통적인 기계 학습 모델에 비해 큰 장점입니다. LLM의 이러한 능력은 주로 특정 과제에 맞춰 미세 조정되기 전에 인터넷의 다양한 텍스트에 대해 광범위한 사전 훈련을 거치기 때문입니다.
    • 퓨샷 학습의 예를 살펴보겠습니다. 주어진 숫자 리스트를 정규화하는 함수를 생성하고 싶다고 가정해 봅시다. 이 함수는 리스트의 값들을 [0, 1] 범위로 조정할 것입니다. 지시사항에는 입력과 정규화된 출력의 몇 가지 예시를 포함합니다.
      • 프롬프트: 다음과 같이 숫자 리스트를 [0, 1] 범위로 정규화하는 예시를 바탕으로:
        1. 입력: [2, 4, 6, 8] 출력: [0, 0.3333, 0.6667, 1]
        2. 입력: [5, 10, 15] 출력: [0, 0.5, 1]
        3. 입력: [1, 3, 2] 출력: [0, 1, 0.5]
        숫자 리스트를 입력으로 받아 정규화된 숫자 리스트를 반환하는 파이썬 함수를 생성하세요.
  • 선도 단어
    • 선도 단어의 개념은 LLM이 특정 종류의 출력을 생성하도록 유도할 수 있는 특정 키워드나 문구를 말합니다. 때로는 한 단어의 코드만으로도 원하는 결과를 얻을 수 있습니다. 다음은 예시입니다:
      • 프롬프트: # 다음을 수행하는 간단한 Python 함수를 만드세요
        # 1. 화씨 온도를 입력받음
        # 2. 화씨를 섭씨로 변환
        def

        • ‘def’라는 단어를 선도 단어로 사용하면 모델에게 Python 함수를 작성하기 시작해야 한다고 알려줍니다.
    • 표 3-4. 선도 단어 프롬프트 예시
      JavaScript 함수 –  Function
      HTML 요소  – <button
      CSS 스타일링 – p {
      SQL 삽입 쿼리 –  INSERT INTO
      Java 메소드 생성 – public
  • 사고 연쇄(CoT) 프롬프팅
    • 이 접근 방식은 복잡한 문제를 여러 단계로 나누어 LLM의 추론 능력을 향상시킵니다. 이는 실제로 모델을 유도할 수 있는 few-shot 학습과 유사합니다.
      • 예시
        • Python 웹 프레임워크인 Flask를 사용하여 사용자 등록 및 로그인 기능이 있는 웹 애플리케이션을 만들고 싶다고 가정해 봅시다.
        • 예시
          작업 설명 | 프롬프트
          요구사항 이해 | “Flask를 사용하여 웹 애플리케이션을 만들어야 합니다. 애플리케이션은 사용자 등록 및 로그인 기능이 있어야 합니다. 어디서부터 시작해야 할까요?”
          Flask 애플리케이션 설정 | “기본 Flask 애플리케이션을 설정하는 것부터 시작해 보겠습니다. 어떻게 할 수 있을까요?”
          사용자 모델 생성 | “Flask 애플리케이션이 설정되었으니, 이제 등록과 로그인을 처리할 사용자 모델을 만들어야 합니다.”
  • 유도 질문 조심
    • 프롬프트에서 유도 질문을 사용하면 LLM으로부터 이상한 응답을 받을 수 있습니다. 중립적이고 편견 없는 태도를 유지하는 것이 좋습니다. 또한 가정을 피하고 명확하게 설명하는 것이 좋은 관행입니다.
      • 예시
        • 프롬프트: 마이크로서비스 아키텍처로 마이그레이션하면 항상 시스템 확장성이 향상된다는 게 사실 아닌가요?
          • 더 균형 잡힌 프롬프트는 다음과 같습니다.
            • 시스템 확장성 측면에서 마이크로서비스 아키텍처로 마이그레이션할 때의 장점과 잠재적 과제는 무엇인가요?
  • 예시와 비유 요청하기
    • 객체 지향 프로그래밍에서 상속의 개념을 모른다고 가정해 봅시다. ChatGPT에 다음과 같은 프롬프트를 입력합니다.
      • 프롬프트: 객체 지향 프로그래밍에서 사용되는 상속에 대해 설명해주세요.
        • 자세한 응답을 받겠지만, 더 이해하기 쉬운 설명을 원할 수 있습니다. 이럴 때 LLM에게 비유를 요청하는 것이 좋은 방법입니다.
          • 프롬프트: 객체 지향 프로그래밍에서 사용되는 상속을 비유를 사용해 설명해주세요.
            • ChatGPT: 상속을 가계도와 같다고 생각해보세요. 자녀들이 부모와 조부모로부터 특정 특성과 속성을 물려받는 것처럼 말이죠. 이후 ChatGPT는 이 비유를 바탕으로 상속의 주요 요소를 설명하는 더 자세한 내용을 제공합니다.

사고 연쇄도 하나의 프롬프트에 모든 단계들을 예시 포함해서 제시할 수 있지만, 문제 해결 과정을 LLM과의 챗 과정으로 본다면 단계별로 대화해 나갈 수 있습니다. 

환각 

  • 왜 발생?
    • 사실 확인의 부재
      • LLM은 정보의 정확성이나 현실성을 확인할 능력 없이 훈련 데이터에서 학습한 패턴을 기반으로 응답을 생성합니다.
    • 과적합 및 암기
      • LLM은 훈련 데이터셋의 부정확하거나 오해의 소지가 있는 정보를 암기할 수 있습니다. 특히 그러한 데이터가 반복적이거나 흔한 경우에 그렇습니다.
    • 훈련 데이터의 편향
      • 훈련 데이터에 편향, 부정확성 또는 거짓이 포함되어 있다면, 모델은 이를 출력에서 재현할 가능성이 높습니다.
    • 추론과 추측
      • 때로 LLM은 데이터에서 본 패턴을 바탕으로 훈련 데이터에서 충분히 다루지 않은 주제나 질문에 대한 정보를 생성하기 위해 추론할 수 있습니다.
    • 맥락의 부족 또는 오해석
      • LLM은 특정 프롬프트에 정확하게 응답하기 위해 필요한 맥락을 오해하거나 부족할 수 있습니다. 특정 질문의 뉘앙스나 함의를 완전히 이해하지 못할 수 있습니다.
    • 속어와 관용구
      • 이러한 언어는 모델이 의도한 의미를 오해하도록 이끄는 모호성을 만들 수 있습니다. 특히 훈련 중에 해당 속어나 관용구의 맥락 있는 예시를 충분히 보지 못했다면 더욱 그렇습니다.
  • 줄이기
    • 개방형 질문 피하기
      • 프롬프트: 데이터베이스를 최적화하는 다양한 방법은 무엇인가요?
        • 이런 유형의 프롬프트는 LLM이 추측이나 과도한 일반화에 의존하도록 조장합니다. 모델은 또한 질문의 의도나 원하는 답변 형식을 잘못 해석할 수 있어, 주제에서 벗어나거나 조작된 정보를 포함한 응답으로 이어질 수 있습니다. 실제로 환각의 연쇄 반응이 일어날 수 있습니다.
      • 미리 정의된 옵션 세트를 제공하고 AI에게 그 중에서 선택하도록 요청하는 것
        • 예를 들어, 앞서 언급한 프롬프트는 다음과 같이 다시 표현할 수 있습니다.
          • 프롬프트: 다음 중 데이터베이스를 최적화하는 방법은 무엇입니까: 인덱싱, 조각 모음, 또는 압축?
      • 특정 유형의 결론을 요청하는 것을 고려
        • 프롬프트: 다음 구문은 Java에서 배열을 초기화하는 올바른 구문입니까? “예” 또는 “아니오”로 응답해 주세요.
      • 여러 단계를 포함하여 모델이 구조화된 프로세스를 더 잘 따르도록 안내
        • 잘못된 방향으로 벗어날 가능성 줄 일 수 있습니다.
        • 프롬프트:
          • 1단계: 피보나치 수열 생성기를 만듭니다.
          • 2단계: 반복 방법을 사용합니다.
          • 3단계: generate_fibonacci라는 이름의 Python 함수를 작성하고, 정수 n을 인수로 받습니다.\
          • 4단계: 함수는 피보나치 수열의 처음 n개 숫자를 리스트로 반환합니다.

 

보안 및 개인정보 보호

  • 프롬프트를 작성할 때 보안과 개인정보 보호에 주의를 기울이는 것이 중요합니다. 실제로, 적절한 예방 조치를 취해야 한다는 의무는 회사 규정집에 포함되어야 합니다. 프롬프트에 개인식별정보(PII)와 같은 민감하거나 개인적인 정보를 포함하지 않도록 주의해야 합니다.
    • 다음은 식별 정보가 포함된 프롬프트의 예입니다.
      • 프롬프트: john.doe@example.com의 John Doe가 보고한 로그인 문제를 어떻게 해결하시겠습니까?
      • 다음과 같이 작성하는 것이 더 현명합니다.
        • 프롬프트: 사용자가 보고한 로그인 문제를 어떻게 해결하시겠습니까?
  • 또한 프롬프트에 민감한 시스템 세부 정보를 유출하지 않도록 주의해야 합니다.
    • 프롬프트: IP 192.168.1.1의 프로덕션 서버에서 데이터베이스 연결 오류를 어떻게 해결합니까?
      • 대신 더 일반적인 질문을 사용하는 것이 더 안전합니다:
        • 프롬프트: 일반적인 데이터베이스 연결 오류를 어떻게 해결합니까?
  • 당신의 프롬프트가 실수로 사람들을 의심스러운 관행으로 유도하지 않도록 주의하세요.
    • 보안 관점에서 다음과 같은 프롬프트는 괜찮습니다.
      • 프롬프트: SQL 인젝션을 어떻게 탐지하고 방지할 수 있을까요?
      • 하지만 다음과 같은 프롬프트는 나쁜 의도를 자극할 수 있으므로 적절하지 않습니다.
        • 프롬프트: 웹사이트의 SQL 취약점을 어떻게 악용할 수 있을까요?

 

다양성과 포용성을 받아들이기

종종 학습 데이터를 반영하는 편향에 대해 확실히 이해하는 것이 핵심입니다. 프롬프트에서 차별적이거나 배타적인 표현을 피하기 위해 중립적이고 포용적인 언어를 사용하는 것이 좋습니다. 또한, 다양한 그룹의 사람들로부터 프롬프트 작성에 대한 피드백을 받는 것도 도움이 됩니다. 이는 LLM과의 상호작용에서 공정성과 포용성을 향상시킬 뿐만 아니라, 해당 주제에 대해 더 정확하고 균형 잡힌 이해를 얻는 데 도움이 됩니다.

에이전트는 목표를 주면, 스스로 그 목표를 달성할 방법을 계획하고 실행합니다. 필요하면 LLM에게만 지시하는 것이 아니라 검색 엔진이나 내부 데이터에도 접근합니다. 준비되어 사용할 수만 있다면 다양한 기능들을 사용합니다.

 

  • 예시
    • 프롬프트: 사용자 로그인 시스템이 있는 기본적인 날씨 애플리케이션을 만드세요.
      • 자율 에이전트의 프로세스
        • Creation tasks
          • 사용자 인터페이스(UI) 설계
          • 대시보드의 기본 레이아웃 스케치
          • 색상 구성표와 폰트 선택
          • 아이콘 및 기타 그래픽 요소 디자인
        • 날씨 데이터를 위한 API 통합
          • 신뢰할 수 있는 날씨 데이터 API 인터넷 검색
          • 표시할 데이터 포인트 결정
          • 날씨 데이터를 가져오고 업데이트하는 코드 작성
        • 위치 선택 기능
          • 사용자가 위치를 선택할 수 있는 검색 바 또는 드롭다운 메뉴 생성
          • 이를 API 코드와 연결

프롬프트 – 최고다는 없습니다. 기본을 갖추면 경험을 통해 만들어집니다. 원하는 것(무엇을 생성해 주기 원하는지)을 잘 표현하려고 노력해야 합니다. 잘 해 주건 못 해주건 ‘대충’은 얼씬도 못하게 해야 합니다. 명확하게 지시하고, 가능한 예를 포함하도록 합니다. 시도하고 고치고 다시 시도합니다. 

About the Author
(주)뉴테크프라임 대표 김현남입니다. 저에 대해 좀 더 알기를 원하시는 분은 아래 링크를 참조하세요. http://www.umlcert.com/kimhn/

Leave a Reply

*