서울 맛집 챗봇 만들기 : Azure OpenAI + RAG

2025. 6. 21. 16:38MS AI School

ChatGPT나 Gemini, Perplexity 등의 챗봇을 사용하다 보면, 가끔 있지도 않은 사실을 진짜인 것 처럼 대답을 하거나, 특정 분야에 대한 지식을 보유하고 있는 모델이 있었으면 하는 생각이 들곤 할 것이다.

이러한 LLM모델을 내가 직접 개발하기엔 어렵지만, 나에게 딱 맞춘 모델로 튜닝하는 것은 가능하다.

오늘은 Azure OpenAI로 여러가지 데이터를 활용하여 RAG를 적용시킨 챗봇을 개발하는 과정을 담아보았다.

RAG(검색 증강 생성)

RAG는 Retrieval-Augmented Generation의 약자로, 기존의 대규모 언어 모델(LLM)을 확장하여, 주어진 컨텍스트나 질문에 대해 더욱 정확하고 풍부한 정보를 제공하는 방법이다. 모델이 학습 데이터에 포함되지 않은 외부 데이터를 실시간으로 검색하고, 이를 바탕으로 답변을 생성하는 과정을 포함한다. 특히 환각(Hallucination)을 방지하고, 모델이 최신 정보를 반영하거나 더 넓은 지식을 활용할 수 있게 한다.


데이터 업로드

 

Azure portal에 접속 후, 나의 리소스 그룹으로 이동한다.

storage 리소스를 생성을 해주고, 좌측 사이드 바의 데이터 스토리지 > 컨테이너에 접속.

컨테이너를 생성해준 후, RAG에 활용할 데이터를 업로드 해준다.

데이터는 JSON, CSV, 심지어 PDF 더라도 괜찮다.

왜냐하면 모델에게 인덱싱을 해주기 전, 텍스트 임베딩 모델을 활용할 수 있기 때문이다.

 

내가 활용한 데이터는 '서울 열린데이터 광장'에서 '서울 관광 음식' 데이터를 가져와 사용하였다.

https://data.seoul.go.kr/

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

 

데이터 Search

 

이제 스토리지에 업로드한 데이터를 인덱싱 해줄 단계이다.

인덱싱을 하는 방법은 JSON 편집을 통해 내가 직접 인덱싱 방법을 설정해줄 수도 있지만,

내가 일일이 인덱싱 편집, 인덱서 생성 등의 과정을 거치지 않고, 임베딩 모델을 통해 모든 과정을 자동으로 처리할 수 있다.

특히, PDF 파일 같은 경우에는 JSON 편집에 어려움이 있기 때문에 임베딩 모델을 활용하는 것이 좋다.

 

이 과정에는 'Azure AI Search' 리소스를 사용해야 한다.

리소스 그룹에서 검색 후 리소스 생성

본인이 임베딩 모델을 사용하지 않고, 직접 데이터 인덱싱 방법 등을 설정하고 싶으면 '인덱스 추가'를 클릭하여 진행하면 된다.

 

위에서 내가 설명한 것 처럼 임베딩 모델을 사용하여 인덱싱 과정을 진행하려면 먼저 Azure AI Foundry에서 임베딩 모델을 생성해주도록 하자.

나는 text-embedding-3-small 모델을 사용하였다. 해당 모델이 가격 대비 꽤 성능이 잘 나오는 것 같다.

텍스트 임베딩 모델 생성 확인

 

임베딩 모델을 생성했다면, 이전에 생성했던 AI Search 리소스로 접속하여, 데이터 가져오기 및 벡터화를 클릭.

RAG 클릭

 

텍스트 벡터화 부분에서 이전에 생성해둔 텍스트 임베딩 모델을 선택하면 된다.

 

데이터의 크기에 따라서 인덱싱 과정이 오래 걸릴 수도 있다. 

나는 꽤 큰 데이터를 사용했더니, 한 10분 가량 소요된 것 같다.

과정이 완료되면 자동으로 인덱스와 인덱서가 생성이 된 걸 확인 할 수 있다.

 

RAG 모델 테스트

 

이제 다시 AI Foundry로 접속하여 Add Data 를 클릭하여 생성해둔 인덱스로 데이터를 모델에게 인덱싱 시켜준다.

 

이제 데이터를 기반하여 대답을 하는지 확인하기 위해 도봉구 맛집을 추천해달라고 요청해보았다.

 

맛집들 추천과 함께 자세한 정보들을 제공해주고, 참고한 reference가 무엇인지도 확인할 수 있다.

 

로컬 환경으로 RAG 모델 호출하여 사용하기

 

Chat playground에서 'View code'를 클릭하면 로컬 환경으로 나의 모델을 호출할 수 있는 Sample Code가 제공된다.

해당 code를 그대로 가져와 endpoint, API Key 등을 나의 키로 입력해주고, 코드를 조금만 수정해주면 로컬에서 사용할 수 있는 코드를 금방 작성할 수 있다.

 

Endpoint와 API Key를 입력해주고, prompt를 내 input을 받도록 수정해주었다.

 

그리고 기본 Sample Code에는 답변을 completition.to_json()으로 출력을 하게 되어있어서, 출력 결과를 확인한 후 내가 원하는 답변만 출력하도록 수정할 수 있다.

 

내가 필요한 답변은 completion > choices[0] > message > content 경로임을 알 수 있다.

나는 참조한 reference도 함께 출력하게 코드를 수정해주었다.

최종 결과

최종 완성 코드
import os
from openai import AzureOpenAI

# 환경변수 또는 직접 설정
endpoint = os.getenv("ENDPOINT_URL", "YOUR_URL")
deployment = os.getenv("DEPLOYMENT_NAME", "7ai010-gpt-4o-mini")
search_endpoint = os.getenv("SEARCH_ENDPOINT", "YOUR_ENDPOINT")
search_key = os.getenv("SEARCH_KEY", "YOUR_SEARCH_KEY")
search_index = os.getenv("SEARCH_INDEX_NAME", "YOUR_INDEX")
subscription_key = os.getenv("AZURE_OPENAI_API_KEY", "YOUR_API_KEY")

# Azure OpenAI 클라이언트 초기화
client = AzureOpenAI(
    azure_endpoint=endpoint,
    api_key=subscription_key,
    api_version="2025-01-01-preview",
)

# 사용자 프롬프트 입력
user_input = input("질문을 입력하세요: ")

# Chat Prompt 생성
chat_prompt = [
    {
        "role": "user",
        "content": user_input
    }
]

# Completion 요청
completion = client.chat.completions.create(
    model=deployment,
    messages=chat_prompt,
    max_tokens=800,
    temperature=0.7,
    top_p=0.95,
    frequency_penalty=0,
    presence_penalty=0,
    stream=False,
    extra_body={
        "data_sources": [{
            "type": "azure_search",
            "parameters": {
                "endpoint": search_endpoint,
                "index_name": search_index,
                "semantic_configuration": "matzip-index-semantic-configuration",
                "query_type": "semantic",
                "strictness": 2,
                "top_n_documents": 5,
                "authentication": {
                    "type": "api_key",
                    "key": search_key
                }
            }
        }]
    }
)

# 결과 출력
print("\n답변:")
print(completion.choices[0].message.content)

# 참고 문서 출력
citations = completion.choices[0].message.context.get("citations")
for citation in citations :
    if citations:
        print(citation['content'] + '\n')
    else:
        print("\n참조 문서 없음.")