다음을 통해 공유


자습서: 2부 - 프롬프트 흐름 SDK를 사용하여 사용자 지정 채팅 애플리케이션 빌드

이 자습서에서는 프롬프트 흐름 SDK(및 기타 라이브러리)를 사용하여 Contoso Trek이라는 소매 회사를 위한 채팅 앱을 빌드, 구성, 평가, 배포합니다. 사용자의 소매 회사는 야외 캠핑 장비 및 의류를 전문으로 취급합니다. 채팅 앱은 사용자의 제품과 서비스에 관한 질문에 답해야 합니다. 예를 들어, 채팅 앱은 "어떤 텐트가 가장 방수가 잘 되나요?", 또는 "추운 날씨에 사용하기 가장 좋은 침낭은 무엇인가요?"와 같은 질문에 대답할 수 있습니다.

2부에서는 사용자 지정 데이터에 응답을 근거로 하는 RAG(검색 증강 생성)를 추가하여 기본 채팅 애플리케이션을 개선하는 방법을 보여 줍니다. RAG(검색 증강 세대)는 LLM(대규모 언어 모델)이 있는 데이터를 사용하여 데이터와 관련된 답변을 생성하는 패턴입니다. 본 2부에서는 다음을 수행하는 방법을 알아봅니다.

  • 앱에서 사용할 Azure AI Studio에서 AI 모델 배포
  • 사용자 지정 RAG 코드 개발
  • 프롬프트 흐름을 사용하여 채팅 앱 테스트

이 자습서는 3부로 구성된 자습서 시리즈 중 제2부입니다.

필수 조건

애플리케이션 코드 구조

로컬 컴퓨터에 rag-tutorial이라는 폴더를 만듭니다. 이 자습서 시리즈는 각 파일의 콘텐츠를 만드는 과정을 안내합니다. 자습서 시리즈를 완료하면 폴더 구조는 다음과 같습니다.

rag-tutorial/
│   .env
│   build_index.py
│   deploy.py
│   evaluate.py
│   eval_dataset.jsonl
|   invoke-local.py
│
├───copilot_flow
│   └─── chat.prompty
|   └─── copilot.py
|   └─── Dockerfile
│   └─── flow.flex.yaml
│   └─── input_with_chat_history.json
│   └─── queryIntent.prompty
│   └─── requirements.txt
│
├───data
|   └─── product-info/
|   └─── [Your own data or sample data as described in the prerequisites.]

이 자습서의 구현에서는 흐름 구현에 대한 코드 우선 방식인 프롬프트 흐름의 플렉스 흐름을 사용합니다. 입력 함수(copilot.py)를 지정한 다음 흐름에 프롬프트 흐름의 테스트, 평가 및 추적 기능을 사용합니다. 이 흐름은 코드로 되어 있으며 DAG(방향성 비순환 Graph) 또는 기타 시각적 구성 요소가 없습니다. GitHub의 프롬프트 흐름 설명서에서 Flex 흐름을 개발하는 방법에 대해 자세히 알아봅니다.

초기 환경 변수 설정

다양한 코드 조각에서 사용되는 환경 변수 컬렉션이 있습니다. 모두 .env 파일에 추가.

Important

git 리포지토리에서 만드는 경우 실수로 리포지토리에 체크 인하지 않도록 .env(이)가 .gitignore파일에 있는지 확인합니다.

이러한 값으로 시작합니다. 자습서를 진행하면서 몇 가지 값을 더 추가합니다.

  1. .env 파일을 rag-tutorial 폴더에 만듭니다. 다음 변수를 추가합니다.

    AZURE_SUBSCRIPTION_ID=<your subscription id>
    AZURE_RESOURCE_GROUP=<your resource group>
    AZUREAI_PROJECT_NAME=<your project name>
    AZURE_OPENAI_CONNECTION_NAME=<your AIServices or Azure OpenAI connection name>
    AZURE_SEARCH_ENDPOINT=<your Azure Search endpoint>
    AZURE_SEARCH_CONNECTION_NAME=<your Azure Search connection name>
    

자리 표시자를 다음 값으로 바꿉니다.

  • AI Studio의 프로젝트 보기에서 <your subscription id>, <your resource group><your project name>을(를) 찾습니다.

    1. AI 스튜디오에서 프로젝트로 이동하여 왼쪽 창에서 설정을 선택합니다.
    2. 프로젝트 속성 섹션에서 구독 ID리소스 그룹을 찾으세요. 이름 필드는 <your project name>입니다
  • 프로젝트 설정에서 연결된 리소스 섹션에서 Azure AIServices 또는 Azure OpenAI에 대한 항목이 표시됩니다. 이름을 선택하여 연결 세부 정보를 엽니다. 연결 이름이 연결 세부 정보 페이지의 맨 위에 나타납니다. <your AIServices or Azure OpenAI connection name>에 사용하려면 이 이름을 복사합니다.

  • 프로젝트 설정 페이지로 돌아갑니다. 연결된 리소스 섹션에서 Azure AI Search에 대한 링크를 선택합니다.

    • <your Azure Search endpoint>대상 URL을 복사합니다.
    • <your Azure Search connection name>의 상단에 있는 이름을 복사합니다.

    엔드포인트 및 연결 이름을 보여 주는 스크린샷.

모델 배포

RAG 기반 채팅 앱을 빌드하려면 Azure OpenAI 채팅 모델(gpt-3.5-turbo)과 Azure OpenAI 포함 모델(text-embedding-ada-002)의 두 가지 모델이 필요합니다. 각 모델에 대해 이 단계 집합을 사용하여 Azure AI Studio 프로젝트에 이러한 모델을 배포합니다.

다음 단계에서는 AI Studio 모델 카탈로그에서 실시간 엔드포인트에 모델을 배포합니다.

  1. AI 스튜디오에 로그인하고 페이지로 이동합니다.

  2. 왼쪽 사이드바에서 모델 카탈로그를 선택합니다.

  3. 컬렉션 필터에서 Azure OpenAI를 선택합니다.

    카탈로그에서 Azure OpenAI 모델로 필터링하는 방법을 보여 주는 스크린샷.

  4. Azure OpenAI 컬렉션에서 모델을 선택합니다. 처음으로 gpt-3.5-turbo 모델을 선택합니다. 두 번째로 text-embedding-ada-002 모델을 선택합니다.

  5. 배포를 선택하여 배포 창을 엽니다.

  6. 모델을 배포할 허브를 선택합니다. 프로젝트와 동일한 허브를 사용합니다.

  7. 배포 이름을 지정하고 요구 사항에 따라 다른 기본 설정을 수정합니다.

  8. 배포를 선택합니다.

  9. 배포 세부 정보 페이지로 이동됩니다. 플레이그라운드에서 열기를 선택합니다.

  10. 코드 보기를 선택하여 배포된 모델을 애플리케이션에서 사용하는 데 사용할 수 있는 코드 샘플을 가져옵니다.

gpt-3.5-turbo 모델을 배포할 때 코드 보기 섹션에서 다음 값을 찾아 .env 파일에 추가합니다.

AZURE_OPENAI_ENDPOINT=<endpoint_value>
AZURE_OPENAI_CHAT_DEPLOYMENT=<chat_model_deployment_name>
AZURE_OPENAI_API_VERSION=<api_version>

text-embedding-ada-002 모델을 배포할 때 .env 파일에 이름을 추가합니다.

AZURE_OPENAI_EMBEDDING_DEPLOYMENT=<embedding_model_deployment_name>

Azure CLI 설치 및 로그인

사용자 자격 증명을 사용하여 Azure OpenAI 서비스를 호출할 수 있도록 Azure CLI를 설치하고 로컬 개발 환경에서 로그인합니다.

대부분의 경우 다음 명령을 사용하여 터미널에서 Azure CLI를 설치할 수 있습니다.

winget install -e --id Microsoft.AzureCLI

특정 운영 체제나 설정에서 이러한 명령이 작동하지 않는 경우 Azure CLI 설치 방법 지침을 따를 수 있습니다.

Azure CLI를 설치한 후 az login 명령을 사용하여 로그인하고 브라우저를 사용하여 로그인합니다.

az login

이제 앱을 만들고 코드에서 Azure OpenAI Service를 호출합니다.

새 Python 환경 만들기

먼저 프롬프트 흐름 SDK 패키지를 설치하는 데 사용할 수 있는 새로운 Python 환경을 만들어야 합니다. 전역 Python 설치에 패키지를 설치하지 마세요. Python 패키지를 설치할 때 항상 가상 환경 또는 conda 환경을 사용해야 합니다. 그렇지 않으면 Python의 전역 설치가 중단될 수 있습니다.

필요한 경우 Python을 설치합니다.

Python 3.10 이상을 사용하는 것이 좋지만 Python 3.8 이상이 필요합니다. 적합한 Python 버전이 설치되어 있지 않은 경우 운영 체제에 Python을 설치하는 가장 쉬운 방법을 알아보려면 VS Code Python 자습서의 지침을 따릅니다.

가상 환경 만들기

이미 Python 3.10 이상이 설치되어 있는 경우 다음 명령을 사용하여 가상 환경을 만들 수 있습니다.

py -3 -m venv .venv
.venv\scripts\activate

Python 환경을 활성화한다는 것은 명령줄에서 python 또는 pip를 실행할 때 애플리케이션의 .venv 폴더에 포함된 Python 인터프리터를 사용하게 된다는 의미입니다.

참고 항목

deactivate 명령을 사용하여 Python 가상 환경을 종료하고 나중에 필요할 때 다시 활성화할 수 있습니다.

Pip 업그레이드

최신 버전의 pip가 있는지 확인하려면 다음 명령을 실행합니다.

python -m pip install --upgrade pip

프롬프트 흐름 SDK 설치

프롬프트 흐름은 아이디어 구상, 프로토타이핑, 테스트, 평가부터 프로덕션 배포 및 모니터링에 이르기까지 LLM 기반 AI 애플리케이션의 엔드투엔드 개발 주기를 간소화하도록 설계된 개발 도구 모음입니다.

pip를 사용하여 만든 가상 환경에 프롬프트 흐름 SDK를 설치합니다.

pip install promptflow
pip install azure-identity

프롬프트 흐름 SDK는 여러 패키지에 대한 종속성을 사용하므로 모든 패키지를 원하지 않는 경우 별도로 설치하도록 선택할 수 있습니다.

  • promptflow-core: LLM 코드 실행에 사용되는 핵심 프롬프트 흐름 런타임을 포함합니다.
  • promptflow-tracing: 표준에서 OpenTelemetry 추적을 내보내는 데 사용되는 경량 라이브러리입니다.
  • promptflow-devkit: 로컬 개발 환경을 위한 프롬프트 흐름 테스트 베드 및 추적 뷰어 도구가 포함되어 있습니다.
  • openai: Azure OpenAI 서비스를 사용하기 위한 클라이언트 라이브러리입니다.
  • python-dotenv: .env 파일에서 환경 변수를 읽어 설정하는 데 사용됩니다.

Azure AI 검색 인덱스 만들기

이 RAG 기반 애플리케이션의 목표는 사용자 지정 데이터에 모델 응답을 근거로 하는 것입니다. 포함 모델의 벡터화된 데이터를 저장하는 Azure AI 검색 인덱스를 사용합니다. 검색 인덱스는 사용자의 질문을 기반으로 관련 문서를 검색하는 데 사용됩니다.

아직 만들어진 Azure AI 검색 인덱스가 없는 경우 인덱스를 만드는 방법을 안내합니다. 사용할 인덱스가 이미 있는 경우 검색 환경 변수 설정 섹션으로 건너뛸 수 있습니다. 검색 인덱스는 이전 단계에서 만들어졌거나 참조된 Azure AI 검색 서비스에 만들어집니다.

  1. 자체 데이터를 사용하거나 ZIP 파일로 예 Contoso Trek 소매 제품 데이터를 다운로드하여 로컬 컴퓨터에 다운로드합니다. rag-tutorial/data 폴더에 파일의 압축을 풉니다. 이 데이터는 제품 정보를 나타내는 markdown 파일의 컬렉션입니다. 데이터는 검색 인덱스로 쉽게 수집할 수 있는 방식으로 구성됩니다. 이 데이터로부터 검색 인덱스를 빌드합니다.

  2. 프롬프트 흐름 RAG 패키지를 사용하면 markdown 파일을 수집하고, 로컬로 검색 인덱스를 만들고, 이를 클라우드 프로젝트에 등록할 수 있습니다. 프롬프트 흐름 RAG 패키지를 설치합니다.

    pip install promptflow-rag
    
  3. rag-tutorial 폴더에 build_index.py 파일을 만듭니다.

  4. 다음 코드를 복사하여 build_index.py 파일에 붙여넣습니다.

    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    from azure.ai.ml import MLClient
    from azure.identity import DefaultAzureCredential
    from azure.ai.ml.entities import Index
    
    from promptflow.rag.config import (
        LocalSource,
        AzureAISearchConfig,
        EmbeddingsModelConfig,
        ConnectionConfig,
    )
    from promptflow.rag import build_index
    
    client = MLClient(
        DefaultAzureCredential(),
        os.getenv("AZURE_SUBSCRIPTION_ID"),
        os.getenv("AZURE_RESOURCE_GROUP"),
        os.getenv("AZUREAI_PROJECT_NAME"),
    )
    import os
    
    # append directory of the current script to data directory
    script_dir = os.path.dirname(os.path.abspath(__file__))
    data_directory = os.path.join(script_dir, "data/product-info/")
    
    # Check if the directory exists
    if os.path.exists(data_directory):
        files = os.listdir(data_directory)  # List all files in the directory
        if files:
            print(
                f"Data directory '{data_directory}' exists and contains {len(files)} files."
            )
        else:
            print(f"Data directory '{data_directory}' exists but is empty.")
            exit()
    else:
        print(f"Data directory '{data_directory}' does not exist.")
        exit()
    
    index_name = "tutorial-index"  # your desired index name
    index_path = build_index(
        name=index_name,  # name of your index
        vector_store="azure_ai_search",  # the type of vector store - in this case it is Azure AI Search. Users can also use "azure_cognitive search"
        embeddings_model_config=EmbeddingsModelConfig(
            model_name=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"),
            deployment_name=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"),
            connection_config=ConnectionConfig(
                subscription_id=client.subscription_id,
                resource_group_name=client.resource_group_name,
                workspace_name=client.workspace_name,
                connection_name=os.getenv("AZURE_OPENAI_CONNECTION_NAME"),
            ),
        ),
        input_source=LocalSource(input_data=data_directory),  # the location of your files
        index_config=AzureAISearchConfig(
            ai_search_index_name=index_name,  # the name of the index store inside the azure ai search service
            ai_search_connection_config=ConnectionConfig(
                subscription_id=client.subscription_id,
                resource_group_name=client.resource_group_name,
                workspace_name=client.workspace_name,
                connection_name=os.getenv("AZURE_SEARCH_CONNECTION_NAME"),
            ),
        ),
        tokens_per_chunk=800,  # Optional field - Maximum number of tokens per chunk
        token_overlap_across_chunks=0,  # Optional field - Number of tokens to overlap between chunks
    )
    
    # register the index so that it shows up in the cloud project
    client.indexes.create_or_update(Index(name=index_name, path=index_path))
    
    • index_name 변수를 원하는 인덱스 ​​이름으로 설정합니다.
    • 필요에 따라 path_to_data 변수를 데이터 파일이 저장된 경로로 업데이트할 수 있습니다.

    Important

    기본적으로 코드 샘플은 이 자습서의 앞부분에 설명된 애플리케이션 코드 구조를 예상합니다. data 폴더는 build_index.py 및 md 파일이 포함된 다운로드한 product-info 폴더와 동일한 수준에 있어야 합니다.

  5. 콘솔에서 코드를 실행하여 로컬로 인덱스를 빌드하고 클라우드 프로젝트에 등록합니다.

    python build_index.py
    
  6. 스크립트가 실행되면 Azure AI 스튜디오 프로젝트의 인덱스 페이지에서 새로 만들어진 인덱스를 볼 수 있습니다. 자세한 내용은 Azure AI 스튜디오에서 벡터 인덱스를 작성하고 사용하는 방법을 참조하세요.

  7. 동일한 인덱스 이름으로 스크립트를 다시 실행하면 동일한 인덱스의 새 버전이 만들어집니다.

검색 인덱스 환경 변수 설정

사용할 인덱스 이름이 있으면(새 인덱스를 만들거나 기존 인덱스를 참조하여) 다음과 같이 이를 .env 파일에 추가합니다.

AZUREAI_SEARCH_INDEX_NAME=<index-name>

사용자 지정 RAG 코드 개발

다음으로 기본 채팅 애플리케이션에 RAG(검색 증강 생성) 기능을 추가하는 사용자 지정 코드를 만듭니다. 빠른 시작에서는 chat.pychat.prompty 파일을 만들었습니다. 여기서는 RAG 기능을 포함하도록 해당 코드를 확장합니다.

RAG를 사용하는 채팅 앱은 다음과 같은 일반적인 논리를 구현합니다.

  1. 사용자 쿼리 의도 및 채팅 기록을 기반으로 검색 쿼리 생성
  2. 포함 모델을 사용하여 쿼리 포함
  3. 쿼리가 주어지면 검색 인덱스에서 관련 문서를 검색합니다.
  4. Azure OpenAI 채팅 완료 모델에 관련 컨텍스트를 전달합니다.
  5. Azure OpenAI 모델에서 응답 반환

채팅 앱 구현 논리

채팅 앱 구현 논리는 copilot.py 파일에 있습니다. 이 파일에는 RAG 기반 채팅 앱에 대한 핵심 논리가 포함되어 있습니다.

  1. rag-tutorial 폴더에 copilot_flow라는 폴더를 만듭니다.

  2. 그런 다음 copilot_flow 폴더에 copilot.py라는 파일을 만듭니다.

  3. copilot.py 파일에 다음 코드를 추가합니다.

    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    from promptflow.core import Prompty, AzureOpenAIModelConfiguration
    from promptflow.tracing import trace
    from openai import AzureOpenAI
    
    # <get_documents>
    @trace
    def get_documents(search_query: str, num_docs=3):
        from azure.identity import DefaultAzureCredential, get_bearer_token_provider
        from azure.search.documents import SearchClient
        from azure.search.documents.models import VectorizedQuery
    
        token_provider = get_bearer_token_provider(
            DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
        )
    
        index_name = os.getenv("AZUREAI_SEARCH_INDEX_NAME")
    
        #  retrieve documents relevant to the user's question from Cognitive Search
        search_client = SearchClient(
            endpoint=os.getenv("AZURE_SEARCH_ENDPOINT"),
            credential=DefaultAzureCredential(),
            index_name=index_name,
        )
    
        aoai_client = AzureOpenAI(
            azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
            azure_ad_token_provider=token_provider,
            api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
        )
    
        # generate a vector embedding of the user's question
        embedding = aoai_client.embeddings.create(
            input=search_query, model=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
        )
        embedding_to_query = embedding.data[0].embedding
    
        context = ""
        # use the vector embedding to do a vector search on the index
        vector_query = VectorizedQuery(
            vector=embedding_to_query, k_nearest_neighbors=num_docs, fields="contentVector"
        )
        results = trace(search_client.search)(
            search_text="", vector_queries=[vector_query], select=["id", "content"]
        )
    
        for result in results:
            context += f"\n>>> From: {result['id']}\n{result['content']}"
    
        return context
    
    
    # <get_documents>
    
    from promptflow.core import Prompty, AzureOpenAIModelConfiguration
    
    from pathlib import Path
    from typing import TypedDict
    
    
    class ChatResponse(TypedDict):
        context: dict
        reply: str
    
    
    def get_chat_response(chat_input: str, chat_history: list = []) -> ChatResponse:
        model_config = AzureOpenAIModelConfiguration(
            azure_deployment=os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT"),
            api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
            azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
        )
    
        searchQuery = chat_input
    
        # Only extract intent if there is chat_history
        if len(chat_history) > 0:
            # extract current query intent given chat_history
            path_to_prompty = f"{Path(__file__).parent.absolute().as_posix()}/queryIntent.prompty"  # pass absolute file path to prompty
            intentPrompty = Prompty.load(
                path_to_prompty,
                model={
                    "configuration": model_config,
                    "parameters": {
                        "max_tokens": 256,
                    },
                },
            )
            searchQuery = intentPrompty(query=chat_input, chat_history=chat_history)
    
        # retrieve relevant documents and context given chat_history and current user query (chat_input)
        documents = get_documents(searchQuery, 3)
    
        # send query + document context to chat completion for a response
        path_to_prompty = f"{Path(__file__).parent.absolute().as_posix()}/chat.prompty"
        chatPrompty = Prompty.load(
            path_to_prompty,
            model={
                "configuration": model_config,
                "parameters": {"max_tokens": 256, "temperature": 0.2},
            },
        )
        result = chatPrompty(
            chat_history=chat_history, chat_input=chat_input, documents=documents
        )
    
        return dict(reply=result, context=documents)
    

copilot.py 파일에는 get_documents()get_chat_response()라는 두 가지 주요 함수가 포함되어 있습니다.

이 두 함수에는 각 함수 호출 입력 및 출력의 프롬프트 흐름 추적 로그를 볼 수 있는 @trace 데코레이터가 있습니다. @trace빠른 시작에서 추적 기능을 보여준 방식에 대한 대안적이고 확장된 방식입니다.

get_documents() 함수는 RAG 논리의 핵심입니다.

  1. 검색 쿼리와 검색할 문서 수를 가져옵니다.
  2. 포함 모델을 사용하여 검색 쿼리를 포함합니다.
  3. Azure Search 인덱스를 쿼리하여 쿼리와 관련된 문서를 검색합니다.
  4. 문서의 컨텍스트를 반환합니다.

get_chat_response() 함수는 chat.py 파일의 이전 논리에서 빌드됩니다.

  1. chat_input 및 모든 chat_history를 사용합니다.
  2. chat_input 의도와 chat_history를 기반으로 검색 쿼리를 구성합니다.
  3. 관련 문서를 검색하려면 get_documents()를 호출합니다.
  4. 쿼리의 근거가 되는 응답을 가져오기 위해 컨텍스트와 함께 채팅 완료 모델을 호출합니다.
  5. 회신과 컨텍스트를 반환합니다. 형식이 지정된 사전을 get_chat_response() 함수의 반환 개체로 설정합니다. 코드가 사용 사례에 가장 적합한 응답을 반환하는 방법을 선택할 수 있습니다.

get_chat_response() 함수는 두 개의 Prompty 파일을 사용하여 다음에서 다루는 필수 LLM(대규모 언어 모델) 호출을 수행합니다.

채팅용 프롬프트 템플릿

chat.prompt 파일은 단순하며 빠른 시작chat.prompt와 유사합니다. 시스템 프롬프트는 제품을 반영하도록 업데이트되었으며 프롬프트 템플릿에는 문서 컨텍스트가 포함됩니다.

  1. copilot_flow 디렉터리에 chat.prompty 파일을 추가합니다. 파일은 제공된 시스템 프롬프트, 채팅 기록 및 문서 컨텍스트와 함께 채팅 완료 모델에 대한 호출을 나타냅니다.

  2. 다음 코드를 chat.prompty 파일에 추가합니다.

    ---
    name: Chat Prompt
    description: A prompty that uses the chat API to respond to queries grounded in relevant documents
    model:
        api: chat
        configuration:
            type: azure_openai
    inputs:
        chat_input:
            type: string
        chat_history:
            type: list
            is_chat_history: true
            default: []
        documents:
            type: object
    
    ---
    system:
    You are an AI assistant helping users with queries related to outdoor outdooor/camping gear and clothing.
    If the question is not related to outdoor/camping gear and clothing, just say 'Sorry, I only can answer queries related to outdoor/camping gear and clothing. So, how can I help?'
    Don't try to make up any answers.
    If the question is related to outdoor/camping gear and clothing but vague, ask for clarifying questions instead of referencing documents. If the question is general, for example it uses "it" or "they", ask the user to specify what product they are asking about.
    Use the following pieces of context to answer the questions about outdoor/camping gear and clothing as completely, correctly, and concisely as possible.
    Do not add documentation reference in the response.
    
    # Documents
    {{documents}}
    
    {% for item in chat_history %}
    {{item.role}}
    {{item.content}}
    {% endfor %}
    
    user:
    {{chat_input}}
    

채팅 기록에 대한 프롬프트 템플릿

RAG 기반 애플리케이션을 구현하고 있기 때문에 현재 사용자 쿼리뿐만 아니라 채팅 기록을 고려하여 관련 문서를 검색하는 데 몇 가지 추가 논리가 필요합니다. 이 추가 논리가 없으면 LLM 호출이 채팅 기록을 고려합니다. 그러나 해당 컨텍스트에 맞는 문서를 검색하지 못하므로 예상한 응답을 가져오지 못할 것입니다.

예를 들어, 사용자가 "이것은 방수 제품인가요?"라고 질문하는 경우 시스템은 채팅 기록을 보고 "이것"이라는 단어가 무엇을 의미하는지 파악하고 해당 컨텍스트를 검색 쿼리에 포함하여 포함해야 합니다. 이런 식으로 "이것"(아마도 Alpine 탐험가 텐트)과 그 "비용"에 대한 올바른 문서를 검색합니다.

포함할 사용자의 쿼리만 전달하는 대신 모든 채팅 기록을 고려하는 새로운 검색 쿼리를 생성해야 합니다. 특정 프롬프트를 작성한 또 다른 Prompty(또 다른 LLM 호출)를 사용하여 채팅 기록에 따른 사용자 쿼리 의도를 해석하고 필요한 컨텍스트가 있는 검색 쿼리를 구성합니다.

  1. copilot_flow 폴더에 queryIntent.prompty 파일을 만듭니다.

  2. 프롬프트 형식과 퓨샷 예에 대한 구체적인 세부 정보를 보려면 이 코드를 입력합니다.

    ---
     name: Chat Prompt
     description: A prompty that extract users query intent based on the current_query and chat_history of the conversation
     model:
         api: chat
         configuration:
             type: azure_openai
     inputs:
         query:
           type: string
         chat_history:
             type: list
             is_chat_history: true
             default: []
     
     ---
     system:
     - You are an AI assistant reading a current user query and chat_history.
     - Given the chat_history, and current user's query, infer the user's intent expressed in the current user query.
     - Once you infer the intent, respond with a search query that can be used to retrieve relevant documents for the current user's query based on the intent
     - Be specific in what the user is asking about, but disregard parts of the chat history that are not relevant to the user's intent.
     
     Example 1:
     With a chat_history like below:
     \```
     chat_history: [    {
           "role": "user",
           "content": "are the trailwalker shoes waterproof?"
         },
         {
           "role": "assistant",
           "content": "Yes, the TrailWalker Hiking Shoes are waterproof. They are designed with a durable and waterproof construction to withstand various terrains and weather conditions."
         }
     ]
     \```
     User query: "how much do they cost?"
     
     Intent: "The user wants to know how much the Trailwalker Hiking Shoes cost."
     Search query: "price of Trailwalker Hiking Shoes"
     
     
     Example 2:
     With a chat_history like below:
     \```
     chat_history: [    {
           "role": "user",
           "content": "are the trailwalker shoes waterproof?"
         },
         {
           "role": "assistant",
           "content": "Yes, the TrailWalker Hiking Shoes are waterproof. They are designed with a durable and waterproof construction to withstand various terrains and weather conditions."
         },
         {
           "role": "user",
           "content": "how much do they cost?"
         },
         {
           "role": "assistant",
           "content": "The TrailWalker Hiking Shoes are priced at $110."
         },
         {
           "role": "user",
           "content": "do you have waterproof tents?"
         },
         {
           "role": "assistant",
           "content": "Yes, we have waterproof tents available. Can you please provide more information about the type or size of tent you are looking for?"
         },
         {
           "role": "user",
           "content": "which is your most waterproof tent?"
         },
         {
           "role": "assistant",
           "content": "Our most waterproof tent is the Alpine Explorer Tent. It is designed with a waterproof material and has a rainfly with a waterproof rating of 3000mm. This tent provides reliable protection against rain and moisture."
         }
     ]
     \```
     User query: "how much does it cost?"
     
     Intent: "the user would like to know how much the Alpine Explorer Tent costs"
     Search query: "price of Alpine Explorer Tent"
     
     {% for item in chat_history %}
     {{item.role}}
     {{item.content}}
     {% endfor %}
     
     Current user query:
     {{query}}
     
     Search query:
    

queryIntent.prompty 파일의 단순한 시스템 메시지는 RAG 솔루션이 채팅 기록과 함께 작동하는 데 필요한 최소 요구 사항을 충족합니다.

필수 패키지 구성

copilot_flow 폴더에 requirements.txt 파일을 만듭니다. 다음 콘텐츠를 추가합니다.

openai
azure-identity
azure-search-documents==11.4.0
promptflow[azure]==1.11.0
promptflow-tracing==1.11.0
promptflow-tools==1.4.0
promptflow-evals==0.3.0
jinja2
aiohttp
python-dotenv

이러한 패키지는 흐름이 로컬 및 배포된 환경에서 실행되기 위해 필요합니다.

플렉스 흐름 사용

앞서 언급했듯이 이 구현에서는 흐름 구현에 대한 코드 우선 방식인 프롬프트 흐름의 플렉스 흐름을 사용합니다. 입력 함수(copilot.py에 정의됨)를 지정합니다. 플렉스 흐름 개발에서 자세히 알아봅니다.

이 yaml은 copilot.py에 정의된 get_chat_response 함수인 입력 함수를 지정합니다. 또한 흐름을 실행하는 데 필요한 요구 사항도 지정합니다.

copilot_flow 폴더에 flow.flex.yaml 파일을 만듭니다. 다음 콘텐츠를 추가합니다.

entry: copilot:get_chat_response
environment:
  python_requirements_txt: requirements.txt

프롬프트 흐름을 사용하여 채팅 앱 테스트

프롬프트 흐름의 테스트 기능을 사용하여 샘플 입력에서 채팅 앱이 예상대로 수행되는 방식을 확인합니다. flow.flex.yaml 파일을 사용하면 프롬프트 흐름을 사용하여 지정된 입력으로 테스트할 수 있습니다.

이 프롬프트 흐름 명령을 사용하여 흐름을 실행합니다.

pf flow test --flow ./copilot_flow --inputs chat_input="how much do the Trailwalker shoes cost?"

또는 --ui 플래그를 사용하여 흐름을 대화형으로 실행할 수 있습니다.

pf flow test --flow ./copilot_flow --ui

--ui를 사용하면 대화형 샘플 채팅 환경이 로컬 브라우저에서 창을 엽니다.

  • --ui 플래그를 사용하여 처음 실행하는 경우 옵션에서 채팅 입력 및 출력을 수동으로 선택해야 합니다. 이 세션을 처음 만들 때 채팅 입출력 필드 구성 설정을 선택한 다음 채팅을 시작합니다.
  • 다음에 --ui 플래그를 사용하여 실행하면 세션이 설정을 기억합니다.

샘플 채팅 환경을 보여 주는 스크린샷.

대화형 세션이 끝나면 터미널 창에 Ctrl + C를 입력하여 서버를 중지합니다.

채팅 기록으로 테스트

일반적으로 프롬프트 흐름과 Prompty는 채팅 기록을 지원합니다. 로컬로 제공되는 프런트 엔드에서 --ui 플래그로 테스트하는 경우 프롬프트 흐름이 채팅 기록을 관리합니다. --ui 없이 테스트하는 경우 채팅 기록을 포함하는 입력 파일을 지정할 수 있습니다.

우리 애플리케이션은 RAG를 구현하기 때문에 queryIntent.prompty 파일에 채팅 기록을 처리하기 위한 추가 논리를 추가해야 했습니다.

채팅 기록으로 테스트하려면 copilot_flow 폴더에 input_with_chat_history.json이라는 파일을 만들고 다음 콘텐츠를 붙여넣습니다.

{
    "chat_input": "how much does it cost?",
    "chat_history": [
        {
        "role": "user",
        "content": "are the trailwalker shoes waterproof?"
        },
        {
        "role": "assistant",
        "content": "Yes, the TrailWalker Hiking Shoes are waterproof. They are designed with a durable and waterproof construction to withstand various terrains and weather conditions."
        },
        {
        "role": "user",
        "content": "how much do they cost?"
        },
        {
        "role": "assistant",
        "content": "The TrailWalker Hiking Shoes are priced at $110."
        },
        {
        "role": "user",
        "content": "do you have waterproof tents?"
        },
        {
        "role": "assistant",
        "content": "Yes, we have waterproof tents available. Can you please provide more information about the type or size of tent you are looking for?"
        },
        {
        "role": "user",
        "content": "which is your most waterproof tent?"
        },
        {
        "role": "assistant",
        "content": "Our most waterproof tent is the Alpine Explorer Tent. It is designed with a waterproof material and has a rainfly with a waterproof rating of 3000mm. This tent provides reliable protection against rain and moisture."
        }
    ]
    }

이 파일로 테스트하려면 다음을 실행합니다.

pf flow test --flow ./copilot_flow --inputs ./copilot_flow/input_with_chat_history.json

예상되는 출력은 다음과 같습니다. "Alpine 탐험가 텐트의 가격은 $350입니다."

이 시스템은 "이것은 얼마인가요?"라는 질문의 의도를 해석할 수 있습니다. "이것"은 채팅 기록의 최신 컨텍스트였던 Alpine 탐험가 텐트를 의미한다는 것을 알고 있습니다. 그런 다음 시스템은 Alpine 탐험가 텐트의 비용에 대한 관련 문서를 쿼리하기 위해 Alpine 탐험가 텐트의 가격에 대한 검색 쿼리를 구성하고 응답을 가져옵니다.

이 흐름 실행에서 추적으로 이동하면 대화가 작동하는 것을 볼 수 있습니다. 로컬 추적 링크는 흐름 테스트 실행 결과 전에 콘솔 출력에 표시됩니다.

스크린샷은 프롬프트 흐름에 대한 콘솔 출력을 보여줍니다.

리소스 정리

불필요한 Azure 비용이 발생하지 않도록 하려면 이 자습서에서 만든 리소스가 더 이상 필요하지 않은 경우 삭제해야 합니다. 리소스를 관리하려면 Azure Portal을 사용할 수 있습니다.

하지만 이 자습서 시리즈의 다음 부분에서 Azure에 채팅 앱을 배포하려는 경우 아직 삭제하지 마세요.

다음 단계