본문 바로가기
  • think normal
새로워지기/마흔의 생활코딩

LangChain | 한국어 llm 모델, LangServe, local ollama, NGROK 배포

by 청춘만화 2024. 4. 26.

llm 관련 다양한 프레임워크들이 있는데 대부분이 OpenAI API 에 의존하고 있다. 되도록 이를 피하는 예제를 위주로 실습 중인데 테디노트에서 관심있는 주요 항목들에 대한 실습 영상을 올려주셔서 관련 진행 내용을 포스팅해본다

무료로 한국어🇰🇷 파인튜닝 모델 받아서 나만의 로컬 LLM 호스팅 하기(LangServe) + RAG 까지!! (학습 콘텐츠 출처: 테디노트)

( 참고로 llm을 로컬 컴퓨터에 다운로드해서 손쉽게 쓸 수 있도록 도와주는 툴은 대표적인 방법은 크게 ollama, AnythingLLM, Lm studio 세가지 정도가 있는데 테디노트의 이번 예제는 올라마 ollama를 활용하고 있다. )

 

 

실습 과정

1.  HuggingFace-Hub에서 한국어🇰🇷 파인튜닝 모델 받기

1) 실습 모델( EEVE-Korean-Instruct-10.8B-v1.0) 다운로드

      (1) 방법 A. CLI 
        a-1) 작업할 폴더 생성 및 이동 
        a-2) 해당 경로 터미널에서 패키지 설치 : pip install huggingface-hub
        a-3) 그 다음, 터미널 창에 입력할 '다운로드' 명령어 

         - 작성 방법

huggingface-cli download \\
  허깅페이스 모델페이지의 메인타이틀 \\
  모델목록중 다운받으려는 파일이름.gguf \\
  --local-dir 내 컴퓨터 안에 모델을 저장할 위치 \\
  --local-dir-use-symlinks 심볼릭(바로가기)링크로 사용여부

        - 사용 예

huggingface-cli download \\
  heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF \\
  ggml-model-Q5_K_M.gguf \\
  --local-dir /Users/chanwoo/code/langserve_ollama/ollama-modelfile \\
  --local-dir-use-symlinks False

 

      (2) 방법 B. 사이트에서 바로 다운
       b-1) 모델 찾기 다운로드
       b-2) 내가 원하는 폴더로 이동  

 

2. 내려받은 모델은 ollama에 등록하기 

1) 선 요약 :   

     (1) 내려받은 .gguf 파일*이 있는 폴더(위치 중요 x) 안에   
     (2) 모델을 설명하는 파일(Modelfile**, 이름 중요 x)을 생성하고
     (3) ollama create를 실행해서 ollama list안에 추가한다  

*.gguf 파일은 나온지 1년도 안된 파일형식(관련 설명 참고)이다. 그래서 종종 HuggingFace-Hub에서 검색한 일부 모델의 경우,, .ggml 파일 형식만 있는 경우도 있으니 알아두면 정신건강에 좋다
 

What is GGUF and GGML?

GGUF and GGML are file formats used for storing models for inference, especially in the context of language models like GPT (Generative…

medium.com

**Modelfile은 확장자 없는 파일이다. VIM..이런 툴도 많은데.. 그냥 VSCODE에서 작성하는게 정신건강에 좋다.. 

***ollama create.. 는 올라마 ollama cli 문법 중 하나인데.. 공식 문서를 보면 상세히 설명이 되어 있다. 
 

ollama/docs/modelfile.md at main · ollama/ollama

Get up and running with Llama 3, Mistral, Gemma, and other large language models. - ollama/ollama

github.com

 

2) 내려받은 .gguf 파일이 있는 경로에 모델 파일 생성 후 작성하기 :

FROM ggml-model-Q5_K_M.gguf

TEMPLATE """
        {{- if .System }}
        <s>{{ .System }}</s>
        {{- end }}
        <s>Human:
        {{ .Prompt }}</s>
        <s>Assistant:
    """

SYSTEM """
        A chat between a curious user and an artificial intelligence assistant. 
        The assistant gives helpful, detailed, and polite answers to the user's questions.
    """

PARAMETER temperature 0
PARAMETER num_predict 3000
PARAMETER num_ctx 4096
PARAMETER stop <s>
PARAMETER stop </s>
Modelfile 모델파일 작성법 요약

FROM               - 사용할 기본 모델 기록(요거만, 필수
TEMPLATE      - 모델에 전송될 전체 프롬프트 템플릿
                        ( *필수는 아니지만 llm이 취한?것 처럼 느껴질 수 있음)
SYSTEM          - 템플릿에 설정될 시스템 메시지를 지정
                        ( *필수는 아니지만 llm?이 나를 무시?하는 것 처럼 느껴질 수 있음)
PARAMETER   - Ollama가 모델을 실행하는 방법에 대한 매개변수를 설정
                        ( *창의/랜덤 정도를 얼마나 줄지 : 0 ~ 2)
ADAPTER        - 절대/상대경로 설정
                        ( *앞에서 --local-dir-use-symlinks False한 경우나 바로 다운로드 한 경우엔 생략) 
LICENSE          - 법적 라이센스를 지정합니다. 
MESSAGE        - 메시지 기록을 지정합니다.
                        ( *특정 상황에 대한 대답 패턴을 정의할 수 있음, 공식문서 참고. 공식문서의 예 
                        "나라의 수도를 확인하는 질문"에는 "Yes/No 단답형으로 답변"할 것을 지정하고 있음) 

*순서는 상관없음. 대소문자 구분안함. 대문자 표기는 사람보기 편하기 위해 쓰임 

 

3) 터미널에서 ollama create 하기 :    

    (1) 사용 예 :

ollama create EEVE-Korean-10.8B -f ollama-modelfile/Modelfile-V02

    (2) 사용 설명 : 

       상황.1) 터미널이 모델 파일과 같은 경로에 있다면, 

ollama create 내가원하는모델이름 -f 모델파일이름

       상황.2) 터미널이 모델 파일과 다른 폴더 경로에 있다면, 

ollama create 내가원하는모델이름 -f 모델이있는폴더(사람마다 다름)/모델파일이름(사람마다 다른데 보통 Modelfile로 사용)

 

4) 잘 등록되었는지 확인, 

   (1)터미널에서 ollama로 내가등록한모델 검색하기
  *참고로 내 컴퓨터에 ollama가 설치되어 있다면 아무 위치에서나 ollama관련 명령을 실행할 수 있음.(이유는 설치 시 환경 변수에 등록되어 있어서임)

ollama list

  (2) 목록에 있으면 성공

 

 

3. 본격 코딩

0) 가상 환경 설정

필수는 아니다. 하지만 개인적으로 가상환경 설정없이 로컬에 바로 의존성 패키지를 설치해버리면 나중에 관리가 안되는 경우가 많아 되도록 습관을 들이려한다. 관리가 안되는 대표적인 사례는 스테이블 디퓨전은 파이썬 최신 버전이 아닌,  3.10.6 버전에 최적화되어있다. 그 밖에 생각보다 많은 실습 예제들이 파이썬 또는 개별 패키지의 특정 버전에 의존하고 있는 사례가 많으니 정신건강을 위해 처음부터 프로젝트 단위로 가상환경을 설치한 후 스터디를 하는 편이 좋은 것 같다. 

1) 빈 폴더 생성 후 
2) 가상 환경 생성 : python -m venv langServe_localRag
3) 가상 환경 실행 : source langServe_localRag/bin/activate 

*langServe_localRag는 각자 원하는 이름으로 설정

      *사례 추가 :  이번  랭서브 langserve 실습을 하면서도 특정 패키지의 버그로 인한 이슈가 있었다..
      LangServe Bug  : 0.1.0에서 이슈 발생 -> 0.1.1으로 패치 업데이트 -> 정상 동작)

 

1) 의존성 패키지 구성하기  

pip install -r requirements.txt

   *참고로  requirements는 내가 개인적으로 구성한 목록이다. 개인이 만든 코드를 본인을 비롯해 그 밖에 다른 이들이 동일한 조건에서 사용할 수 있도록 하는 문서이고 이 문서를 생성하는 방법은 해당 프로젝트 디랙토리 터미널에서 아래 코드를 실행하면 된다.

pip freeze > requirements.txt

 

2) 패키지/모듈/클래스 구성

import os   #운영 체제와 상호 작용하기 위한 수십 가지 함수를 제공하는 모듈
from fastapi import FastAPI #빠르고 사용하기 쉬운 ASGI 웹 프레임워크로 API 생성에 사용
from fastapi.responses import RedirectResponse  #응답 클래스 중 하나로, 특정 URL로 리다이렉션 수행
from fastapi.middleware.cors import CORSMiddleware  #CORS는 Cross-Origin Resource Sharing의 약자로, 다른 출처 간의 자원 공유 시 사용 
from typing import List, Union  #Python의 typing 모듈은 코드에 타입 @힌트를 추가하기 위한 용도 
from langserve.pydantic_v1 import BaseModel, Field  #데이터 검증을 위한 클래스입니다.
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage  #메시지(사람, AI, 시스템)를 다루는 클래스를 임포트
from langserve import add_routes    #FastAPI 애플리케이션에 라우트를 추가하는 함수
from langchain_community.chat_models import ChatOllama  #Ollama 채팅 모델을 핸들링하는 클래스
from langchain_core.output_parsers import StrOutputParser   #출력을 파싱하여 문자열 형태로 만드는 클래스
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder  #langchain_core.prompts로부터 프롬프트를 관리하는 클래스를 가져옮

[ 참고 ]

  • ChatPromptTemplate:
    • 이는 채팅 모델에게 특정 작업을 지시하는 프롬프트를 생성하는 데 사용되는 클래스입니다.
    • 예를 들어, 사용자로부터 주제를 받아 "주제에 대해 설명해주세요."와 같은 프롬프트를 생성합니다.
  • MessagesPlaceholder:
    • 이는 프롬프트 템플릿 안에서 동적으로 변하는 부분을 나타내기 위한 클래스입니다.
    • 예를 들어, 사용자의 질문이나 기타 메시지를 프롬프트 템플릿에 포함시킬 때 사용됩니다. 따라서 이는 사용자의 메시지에 따라 바뀌는 부분을 템플릿에 삽입하는데 사용됩니다.

 

3) env 로드

from dotenv import load_dotenv

# .env 파일 내의 변수들을 로드합니다.
load_dotenv()

[ 참고 ]
root(맨 위) 폴더에 이름 없이 .env라는 파일을 생성한 후 아래와 같이 작성하는데 아래 내용 중 'ls__1111aa 22bb3 3444cc 5666dd 77e' 부분만 본인이 발급받은 api키를 넣으면 된다.

LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__11111aa22222bb334444cc55666dd77e
LANGCHAIN_PROJECT=MY_LANGSERVE_PROJECT

참고로 랭체인에서 키를 발급하는 페이지는 여기( -> link) 이다  

 

LangSmith

 

smith.langchain.com

 

4) 코드1. 클라이언트의 요청에 따라 특정 작업 수행 및 결과 반환 

# Ollama 로드 : LangChain의 챗모델 
llm = ChatOllama(model="EEVE-Korean-10.8B:latest")

# 프롬프트를 생성 : 사용자가 주어진 주제에 대해 설명하도록 지시하는 프롬프트를 만듭니다.
prompt_prompt = ChatPromptTemplate.from_template("{topic} 에 대하여 간략히 설명해 줘.")

# 체인 구성 : 사용자의 입력을 받아 챗 모델을 거쳐 파싱된 출력 
chain = prompt_prompt | llm | StrOutputParser()

# 번역 체인 구성 : 사용자의 입력을 받은 문장(프롬프트)에 따라 챗모델을 거쳐 파싱된 출력을 만들어내는 역할
EN_TO_KO_chain = translator_prompt | llm | StrOutputParser()

 

5) 프롬프트 구성 

# 채팅 프롬프트 생성 : ChatPromptTemplate의 from_messages 메소드를 사용
chat_prompt = ChatPromptTemplate.from_messages(
    [
        # 이 배열의 첫 번째 요소는 시스템 메시지입니다. 이것은 AI에게 명령을 내립니다.
        # 이 경우에는, 내 이름은 '테디'이고 도움을 주는 AI Assistant임을 명시하고 있습니다. 이 AI는 반드시 한국어로 대답해야 합니다.
        (
            "system",
            "You are a helpful AI Assistant. Your name is '테디'. You must answer in Korean.",
        ),
        # 이 프롬프트는 사용자의 메시지를 저장하는 Placeholder를 포함하고 있습니다.
        # Placeholder는 템플릿에서 변하는 부분을 나타내는데 사용됩니다.
        # 'variable_name' 속성의 값으로 'messages'를 설정하여, 채팅 대화에서 사용자의 메시지를 포함하도록 합니다.
        MessagesPlaceholder(variable_name="messages")
    ]
)

# 'chat_prompt', 'llm', 그리고 'StrOutputParser'를 '|' 연산자를 이용하여 체인으로 연결합니다.
chat_chain = chat_prompt | llm | StrOutputParser()

# 번역 프롬프트 생성 : 주어진 문장들을 한국어로 번역하라는 지시를 가지는 프롬프트를 만듭니다.
translator_prompt = ChatPromptTemplate.from_template(
    "Translate following sentences into Korean:\\n{input}"
)

[참고]

- 'chat_prompt'는 사용자의 입력을 받아 프롬프트를 생성하는 역할 수행 
- 'llm'은 생성된 프롬프트를 이용해 모델에서 대화를 생성하는 역할 수행 
- 'StrOutputParser'는 생성된 대화 결과를 파싱하여 텍스트 형태로 바꾸는 역할 수행 
이렇게 체인을 형성함으로써 사용자의 입력을 받아 대화를 생성하고, 그 결과를 텍스트로 반환하는 전체 과정을 한 라인에 구현

 

6) FastAPI 앱을 초기화, CORS(Cross-Origin Resource Sharing) 설정

- 이 API가 다른 도메인에서도 접근하고 사용(자원 공유)할 수도록 FastAPI인스턴스를 만들어 CORS 미들웨어를 추가

# FastAPI는 Python 웹 프레임워크. API 생성에 사용됨 
app = FastAPI()

# CORS(Cross-Origin Resource Sharing, "*"는 모든 출처를 허용한다는 의미입니다.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"],
)

 

7) RESTful API 서버를 설정하고 실행

# 루트 URL("/")에 GET 요청이 왔을 경우 "/prompt/playground"로 리다이렉트
@app.get("/")
async def redirect_root_to_docs():
    return RedirectResponse("/prompt/playground")

 

8) 개별 서비스 구성

- 다양한 경로("/prompt", "/chat", "/translate", "/llm")를 설정하여 해당 경로로 REQUEST 요청이 들어왔을 때, 적절한 방식으로 처리하고 응답하는 기능 구성

# 1. 기본 Q&A 체인의 기능에 접근할 수 있도록 "/prompt" URL을 구성 
add_routes(app, chain, path="/prompt")


# 2-2. 채팅 엔드포인트를 위한 입력 타입을 정의합니다.
# 이는 대화를 구성하는 메시지 목록이며, 사람의 메시지, AI의 메시지, 시스템 메시지를 포함할 수 있습니다.
class InputChat(BaseModel):
    """Input for the chat endpoint."""
    messages: List[Union[HumanMessage, AIMessage, SystemMessage]] = Field(
        ...,
        description="The chat messages representing the current conversation.",
    )

# 2-1. 채팅 체인(chat_chain)에 접근할 수 있도록 "/chat"라는 경로(URL)로 라우팅
# 추가로, 피드백 엔드포인트, 공개 트레이스 링크 엔드포인트를 활성화하며, 이 엔드포인트의 유형을 "chat"으로 설정    
add_routes(
    app,
    chat_chain.with_types(input_type=InputChat),
    path="/chat",
    enable_feedback_endpoint=True,
    enable_public_trace_link_endpoint=True,
    playground_type="chat",
)

# 3. 번역 체인(EN_TO_KO_chain)을 "/translate"라는 경로URL로 라우팅
add_routes(app, EN_TO_KO_chain, path="/translate")

# 4. ChatOllama 모델에 직접 접근할 수 있는 "/llm"라는 경로 라우팅
add_routes(app, llm, path="/llm")

 

9) app.py 코드를 실행하면, 자동으로 uvicorn을 통해 이 애플리케이션(FastAPI 서버)이 동작하도록 세팅 

# 메인 프로그램으로 실행되었을 때 아래의 코드를 실행합니다.
if __name__ == "__main__":
    
    # uvicorn은 ASGI 서버입니다. FastAPI 애플리케이션을 호스팅하기 위해 사용됩니다.
    # 여기서는 0.0.0.0 주소의 8000 포트에서 애플리케이션을 실행합니다.
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

 

10) NGROK로 배포 

(1) ngrok 가입

 

ngrok | Unified Application Delivery Platform for Developers

ngrok is a secure unified ingress platform that combines your global server load balancing, reverse proxy, firewall, API gateway and Kubernetes Ingress Controller to deliver applications and APIs.

ngrok.com

(2) 도메인 등록 https://dashboard.ngrok.com/cloud-edge/domains

 

ngrok - Online in One Line

 

dashboard.ngrok.com

(3)베이스 모델 및 코드 실행

   (3-1) ollama, app.py 실행 

   (3-2)터미널을 하나 더 열어서,, 

(4) ngrok 에서 터널링(포트 포워드)

    (4-1) 코드 실행 시 사용한 포트와 동일한 값으로 명령어 실행 

    (4-2) 포트포워딩 결과

(5) 접속 

- 기본 Q&A챗 체인에 접속  

https://각자 설정한 주소.ngrok-free.app/prompt/playground/

- 채팅챗 체인에 접속 

https://각자 설정한 주소.ngrok-free.app/chat/playground/

- 랭스미스 LangSmith에서 모니터링 

각자 배정?받은 랭스미스 대시보드 링크 화면

- 번역챗 체인에 접속 

 

 

 

 

Code

https://github.com/normalstory/local_ollama_langserve/tree/main

 

GitHub - normalstory/local_ollama_langserve

Contribute to normalstory/local_ollama_langserve development by creating an account on GitHub.

github.com

 

 

 

댓글