{"id":25009968,"url":"https://github.com/kyopark2014/llama3.2-rag-bot","last_synced_at":"2025-06-21T00:04:19.004Z","repository":{"id":246522979,"uuid":"821374085","full_name":"kyopark2014/llama3.2-rag-bot","owner":"kyopark2014","description":"Multimodal RAG based on Llama 3.2","archived":false,"fork":false,"pushed_at":"2024-11-04T22:35:56.000Z","size":3036,"stargazers_count":28,"open_issues_count":0,"forks_count":10,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-21T00:01:58.853Z","etag":null,"topics":["bedrock","chatbot","langchain","llama3","llama3-1"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kyopark2014.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-28T11:54:28.000Z","updated_at":"2025-05-19T09:12:26.000Z","dependencies_parsed_at":"2024-06-28T13:29:58.805Z","dependency_job_id":"3c54ab76-639d-4037-9107-76b0eb9297fc","html_url":"https://github.com/kyopark2014/llama3.2-rag-bot","commit_stats":null,"previous_names":["kyopark2014/llama3-rag-workshop","kyopark2014/llama3.1-rag-bot","kyopark2014/llama3.2-rag-bot"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kyopark2014/llama3.2-rag-bot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fllama3.2-rag-bot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fllama3.2-rag-bot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fllama3.2-rag-bot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fllama3.2-rag-bot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyopark2014","download_url":"https://codeload.github.com/kyopark2014/llama3.2-rag-bot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fllama3.2-rag-bot/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261039081,"owners_count":23100969,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bedrock","chatbot","langchain","llama3","llama3-1"],"created_at":"2025-02-05T04:52:19.101Z","updated_at":"2025-06-21T00:04:13.994Z","avatar_url":"https://github.com/kyopark2014.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Llama3.2로 RAG를 구현하기 \n\n\u003ca href=\"https://hits.seeyoufarm.com\"\u003e\u003cimg src=\"https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fkyopark2014%2Fllama3.1-rag-bot\u0026count_bg=%2379C83D\u0026title_bg=%23555555\u0026icon=\u0026icon_color=%23E7E7E7\u0026title=hits\u0026edge_flat=false\"/\u003e\u003c/a\u003e\n\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/LICENSE-MIT-green\"\u003e\n\n\n여기에서는 Llama3.2를 이용해 RAG를 구현하는 과정을 설명합니다. 여기에서는 Advanced RAG에서 성능 향상을 위해 활용되는 parent/child chunking, lexical/semantic 검색등이 포함되어 있습니다. 전체적인 Architecture는 아래와 같습니다.\n\n1) 브라우저를 이용하여 CloudFront의 Domain로 접속하면 S3에 있는 html, css, js를 이용해 채팅화면 UI를 구성합니다.\n2) 사용자가 userId를 넣고 접속하면, DynamoDB에 저장된 과거의 채팅이력을 조회하여 화면에 표시합니다.\n3) 사용자가 채팅창에서 메시지를 입력하면 메시지는 WebSocket을 지원하는 API-Gateway를 통해 Lambda (chat)에 전달됩니다.\n4) Lambda(chat)은 userId로 된 채팅이력이 있는지 조회하여 로드합니다.\n5) 채팅 이력과 현재의 질문을 조합하여 새로운 질문을 만든후에, Embedding후 Vector store인 OpenSearch에 조회합니다.\n6) 새로운 질문(Revised Question)과 RAG로 얻어진 관련된 문서들(Relevant documents)를 context로 Llama3 LLM에 답변을 요청합니다.\n7) Llama3가 생성한 답변은 Lambda (chat)과 API Gateway를 거쳐서 Client에 Websocket으로 전달됩니다. \n\n\u003cimg src=\"./images/basic-architecture.png\" width=\"800\"\u003e\n\n## Llama3 RAG 구현 \n\n### LangChain 설정\n\nLangChain의 [ChatBedrock](https://python.langchain.com/v0.2/docs/integrations/chat/bedrock/)로 API을 이용합니다.\n\n```python\nboto3_bedrock = boto3.client(\n    service_name='bedrock-runtime',\n    region_name=bedrock_region,\n    config=Config(\n        retries = {\n            'max_attempts': 30\n        }\n    )\n)\nparameters = {\n    \"max_gen_len\": 1024,  \n    \"top_p\": 0.9, \n    \"temperature\": 0.1\n}    \n\nchat = ChatBedrock(   \n    model_id=modelId,\n    client=boto3_bedrock, \n    model_kwargs=parameters,\n)\n```\n\nLlama3.1에 대한 모델 정보는 아래와 같습니다. 여기서 Llama3.1의 405B의 모델명은 \"meta.llama3-1-405b-instruct-v1:0\"이고, 70B는 \"meta.llama3-1-70b-instruct-v1:0\" 입니다. \n\n```java\nconst llama3 = [\n  {\n    \"bedrock_region\": \"us-west-2\", // Oregon\n    \"model_type\": \"llama3\",\n    \"model_id\": \"meta.llama3-1-405b-instruct-v1:0\" // \"meta.llama3-1-70b-instruct-v1:0\"\n  }\n];\n```\n\n### Basic Chat\n\nPrompt를 이용해 chatbot의 이름과 Role을 지정할 수 있습니다. Chat history는 MessagesPlaceholder()를 이용해 반영합니다.\n\n\n```python\ndef general_conversation(connectionId, requestId, chat, query):\n    if isKorean(query)==True :\n        system = (\n            \"다음의 Human과 Assistant의 친근한 이전 대화입니다. Assistant은 상황에 맞는 구체적인 세부 정보를 충분히 제공합니다. Assistant의 이름은 서연이고, 모르는 질문을 받으면 솔직히 모른다고 말합니다. 답변은 한국어로 합니다.\"\n        )\n    else: \n        system = (\n            \"Using the following conversation, answer friendly for the newest question. If you don't know the answer, just say that you don't know, don't try to make up an answer. You will be acting as a thoughtful advisor.\"\n        )\n    \n    human = \"{input}\"\n    \n    prompt = ChatPromptTemplate.from_messages([(\"system\", system), MessagesPlaceholder(variable_name=\"history\"), (\"human\", human)])\n    \n    history = memory_chain.load_memory_variables({})[\"chat_history\"]\n                \n    chain = prompt | chat    \n    try: \n        isTyping(connectionId, requestId)  \n        stream = chain.invoke(\n            {\n                \"history\": history,\n                \"input\": query,\n            }\n        )\n        msg = readStreamMsg(connectionId, requestId, stream.content)    \n                            \n        msg = stream.content\n        print('msg: ', msg)\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)        \n        \n    return msg\n```\n\n여기서 Stream은 아래와 같이 event를 추출하여 json format으로 client에 결과를 전달합니다. \n\n```python\ndef readStreamMsg(connectionId, requestId, stream):\n    msg = \"\"\n    if stream:\n        for event in stream:\n            msg = msg + event\n\n            result = {\n                'request_id': requestId,\n                'msg': msg,\n                'status': 'proceeding'\n            }\n            sendMessage(connectionId, result)\n    return msg\n```\n\n### 대화 이력의 관리\n\n사용자가 접속하면, DynamoDB에서 대화 이력을 가져옵니다. 이것은 최초 접속 1회만 수행합니다. \n\n```python\ndef load_chat_history(userId, allowTime):\n    dynamodb_client = boto3.client('dynamodb')\n\n    response = dynamodb_client.query(\n        TableName=callLogTableName,\n        KeyConditionExpression='user_id = :userId AND request_time \u003e :allowTime',\n        ExpressionAttributeValues={\n            ':userId': {'S': userId},\n            ':allowTime': {'S': allowTime}\n        }\n    )\n\n\n```\n\nContext에 넣을 history를 가져와서 memory_chain에 등록합니다.\n\n```pytho\nfor item in response['Items']:\n    text = item['body']['S']\n    msg = item['msg']['S']\n    type = item['type']['S']\n\n    if type == 'text' and text and msg:\n        memory_chain.chat_memory.add_user_message(text)\n        if len(msg) \u003e MSG_LENGTH:\n            memory_chain.chat_memory.add_ai_message(msg[:MSG_LENGTH])                          \n        else:\n            memory_chain.chat_memory.add_ai_message(msg) \n```\n\nLambda와 같은 서버리스는 이벤트가 있을 경우에만 사용이 가능하므로, 이벤트의 userId를 기준으로 메모리를 관리합니다. \n\nmap_chain = dict()\n\n```python\nif userId in map_chain:  \n    memory_chain = map_chain[userId]    \nelse: \n    memory_chain = ConversationBufferWindowMemory(memory_key=\"chat_history\", output_key='answer’,\n              return_messages=True, k=10)\n    map_chain[userId] = memory_chain\n```\n\n새로운 입력(text)과 응답(msg)를 user/ai message로 저장합니다.\n\n```python\nmemory_chain.chat_memory.add_user_message(text)\nmemory_chain.chat_memory.add_ai_message(msg)\n```\n\n### WebSocket Stream 사용하기 \n\n#### Client 동작\n\nWebSocket을 연결하기 위하여 endpoint를 접속을 수행합니다. onmessage()로 메시지를 받습니다. WebSocket이 연결되면 onopen()로 초기화를 수행합니다. 일정 간격으로 keep alive 동작을 수행합니다. 네트워크 재접속 등의 이유로 세션이 끊어지면 onclose()로 확인할 수 있습니다.\n\n```python\nconst ws = new WebSocket(endpoint);\nws.onmessage = function (event) {        \n    response = JSON.parse(event.data)\n\n    if(response.request_id) {\n        addReceivedMessage(response.request_id, response.msg);\n    }\n};\nws.onopen = function () {\n    isConnected = true;\n    if(type == 'initial')\n        setInterval(ping, 57000); \n};\nws.onclose = function () {\n    isConnected = false;\n    ws.close();\n};\n```\n\n발신 메시지는 JSON 포맷으로 아래와 같이 userId, 요청시간, 메시지 타입과 메시지를 포함합니다. 발신시 WebSocket의 send()을 이용하여 발신합니다. 발신시점에 세션이 연결되어 있지 않다면 연결하고 재시도하도록 알림을 표시합니다.\n\n```python\nsendMessage({\n    \"user_id\": userId,\n    \"request_id\": requestId,\n    \"request_time\": requestTime,        \n    \"type\": \"text\",\n    \"body\": message.value\n})\nWebSocket = connect(endpoint, 'initial');\nfunction sendMessage(message) {\n    if(!isConnected) {\n        WebSocket = connect(endpoint, 'reconnect');        \n        addNotifyMessage(\"재연결중입니다. 잠시후 다시시도하세요.\");\n    }\n    else {\n        WebSocket.send(JSON.stringify(message));     \n    }     \n}\n```\n\n#### Server 동작\n\nClient로 부터 메시지 수신은 Lambda로 전달된 event에서 connectionId와 routeKey를 이용해 수행합니다. 이때 keep alive 동작을 수행하여 세션을 유지합니다. 메시지 발신은 boto3로 \"apigatewaymanagementapi\"로 client를 정의한 후에 client.post_to_connection()로 전송합니다.\n\n```python\nconnection_url = os.environ.get('connection_url')\nclient = boto3.client('apigatewaymanagementapi’,      \n        endpoint_url=connection_url)\n\ndef sendMessage(id, body):\n    try:\n        client.post_to_connection(\n            ConnectionId=id, \n            Data=json.dumps(body)\n        )\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('err_msg: ', err_msg)\n        raise Exception (\"Not able to send a message\")\n\ndef lambda_handler(event, context):\n    if event['requestContext']: \n        connectionId = event['requestContext']['connectionId']        \n        routeKey = event['requestContext']['routeKey']\n        \n        if routeKey == '$connect':\n            print('connected!')\n        elif routeKey == '$disconnect':\n            print('disconnected!')\n        else:\n            body = event.get(\"body\", \"\")\n            if body[0:8] == \"__ping__\":  # keep alive\n                sendMessage(connectionId, \"__pong__\")\n            else:\n                msg, reference = getResponse(connectionId, jsonBody)\n```\n\n### Prompt 사용 예: 번역하기\n\nPrompt Engineering을 이용하여 손쉽게 한/영 번역을 수행합니다.\n\n```python\ndef translate_text(chat, text):    \n    if isKorean(text)==True:        \n        system = (\n            \"You are a helpful assistant that translates Korean to English in \u003carticle\u003e tags. Put it in \u003cresult\u003e tags.\"\n        )\n    else:\n        system = (\n            \"다음의 \u003carticle\u003e tags의 내용을 한국어로 번역하세요. 결과는 \u003cresult\u003e tag를 붙여주세요.\"\n        )\n        \n    human = \"\u003carticle\u003e{text}\u003c/article\u003e\"\n    \n    prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n    print('prompt: ', prompt)\n    \n    chain = prompt | chat    \n    try: \n        result = chain.invoke(\n            {\n                \"text\": text\n            }\n        )\n        \n        msg = result.content\n        print('translated text: ', msg)\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)                    \n        raise Exception (\"Not able to request to LLM\")\n\n    msg = result.content\n    return msg[msg.find('\u003cresult\u003e')+8:len(msg)-9] # remove \u003cresult\u003e tag\n```\n\n\n### Prompt 사용 예: 문법 오류 고치기\n\nPrompt Engineering을 이용해서 한/영 문법 오류 고치는 API를 만들 수 있습니다.\n\n```python\ndef check_grammer(chat, text):\n    if isKorean(text)==True:\n        system = (\n            \"다음의 \u003carticle\u003e tag안의 문장의 오류를 찾아서 설명하고, 오류가 수정된 문장을 답변 마지막에 추가하여 주세요.\"\n        )\n    else: \n        system = (\n            \"Here is pieces of article, contained in \u003carticle\u003e tags. Find the error in the sentence and explain it, and add the corrected sentence at the end of your answer.\"\n        )\n        \n    human = \"\u003carticle\u003e{text}\u003c/article\u003e\"\n    \n    prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n```    \n\n### Prompt 사용 예: 코드 요약하기\n\nPrompt Engineering을 이용해서 코드 요약하기 API를 만들 수 있습니다.\n\n```python\ndef summary_of_code(chat, code, mode):\n    if mode == 'py':\n        system = (\n            \"다음의 \u003carticle\u003e tag에는 python code가 있습니다. code의 전반적인 목적에 대해 설명하고, 각 함수의 기능과 역할을 자세하게 한국어 500자 이내로 설명하세요.\"\n        )\n    elif mode == 'js':\n        system = (\n            \"다음의 \u003carticle\u003e tag에는 node.js code가 있습니다. code의 전반적인 목적에 대해 설명하고, 각 함수의 기능과 역할을 자세하게 한국어 500자 이내로 설명하세요.\"\n        )\n    \n    human = \"\u003carticle\u003e{code}\u003c/article\u003e\"\n    \n    prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n```\n\n### RAG\n\nRAG에서는 context tag를 이용해 Relevant Documents를 넣도록  Prompt를 구성합니다. \n\n```python\ndef query_using_RAG_context(connectionId, requestId, chat, context, revised_question):    \n    system = (\n            \"\"\"다음의 \u003ccontext\u003e tag안의 참고자료를 이용하여 상황에 맞는 구체적인 세부 정보를 충분히 제공합니다. Assistant의 이름은 서연이고, 모르는 질문을 받으면 솔직히 모른다고 말합니다.\n            \n            \u003ccontext\u003e\n            {context}\n            \u003c/context\u003e\"\"\"\n        )\n    \n    human = \"{input}\"\n    \n    prompt = ChatPromptTemplate.from_messages([(\"system\", system), (\"human\", human)])\n```\n\nHistory를 이용한 revised question과 Stream을 활용해서 성능 및 사용성을 높입니다.\n\n```python\n    chain = prompt | chat\n    \n    stream = chain.invoke(\n        {\n            \"context\": context,\n            \"input\": revised_question,\n        }\n    )\n    msg = readStreamMsg(connectionId, requestId, \n            stream.content)    \n\n    return msg\n```\n\nOpenSearch를 이용해 Vector Store를 정의하고, 읽어온 문서를 등록합니다.\n\n```python\ndef store_document_for_opensearch(bedrock_embeddings, docs, documentId):\n        delete_document_if_exist(metadata_key)\n\n        vectorstore = OpenSearchVectorSearch(\n            index_name=index_name,  \n            is_aoss = False,\n            #engine=\"faiss\",  # default: nmslib\n            embedding_function = bedrock_embeddings,\n            opensearch_url = opensearch_url,\n            http_auth=(opensearch_account, opensearch_passwd),\n        )\n        response = vectorstore.add_documents(docs, bulk_size = 2000)\n```\n\nVectorstore를 통해 관련된 문서를 추출하여 context로 활용합니다.\n\n```python\n# vector search (semantic) \n    relevant_documents = vectorstore_opensearch.similarity_search_with_score(\n        query = query,\n        k = top_k,\n    )\nrelevant_docs = [] \nif(len(rel_docs)\u003e=1):\n        for doc in rel_docs:\n            relevant_docs.append(doc)\n\n    for document in relevant_docs:\n        content = document['metadata']['excerpt']\n                \n        relevant_context = relevant_context + content + \"\\n\\n\"\n\nmsg = query_using_RAG_context(connectionId, requestId, chat, relevant_context, revised_question)\n```\n\n### RAG의 Parent/Child Chunking\n\n문서를 크기에 따라 parent chunk와 child chunk로 나누어서 child chunk를 찾은 후에 LLM의 context에는 parent chunk를 사용하면, 검색의 정확도는 높이고 충분한 문서를 context로 활용할 수 있습니다. RAG의 검색정확도를 향상시키기 위한 여러가지 방법중에 Parent/Child Chunking을 이용할 수 있습니다. [parent-document-retrieval.md](https://github.com/kyopark2014/korean-chatbot-using-amazon-bedrock/blob/main/parent-document-retrieval.md)에서는 Parent/child로 chunking 전략에 대해 설명하고 있습니다.\n\n```python\nparent_splitter = RecursiveCharacterTextSplitter(\n    chunk_size=2000,\n    chunk_overlap=100,\n    separators=[\"\\n\\n\", \"\\n\", \".\", \" \", \"\"],\n    length_function = len,\n)\nchild_splitter = RecursiveCharacterTextSplitter(\n    chunk_size=400,\n    chunk_overlap=50,\n    # separators=[\"\\n\\n\", \"\\n\", \".\", \" \", \"\"],\n    length_function = len,\n)\n```\n\nParent/Child Chunking을 수행하는 과정은 아래와 같습니다. \n\n1) parent/child로 chunking을 수행합니다.\n\n2) parent doc을 OpenSearch에 add하면, parent_doc_id가 생성됩니다. \n\n3) child doc의 meta에 parent_doc_id를 등록합니다.\n  \n4) 문서 검색시, 필터를 이용해 child 문서를 검색합니다.\n  \n5) 검색된 child 문서들이 parent가 동일하다면 중복을 제거합니다.\n  \n6) parent_doc_id를 이용하여 OpenSearch에서 parent doc을 가져와 context로 활용합니다. \n\nParent chunk의 meta에 “doc_level”을 “parent”로 지정하고 OpenSearch에 등록합니다. \n\n```python\nparent_docs = parent_splitter.split_documents(docs)\n    if len(parent_docs):\n        for i, doc in enumerate(parent_docs):\n            doc.metadata[\"doc_level\"] = \"parent\"\n                    \n        parent_doc_ids = vectorstore.add_documents(parent_docs, bulk_size = 10000)\n```\n\nChild chunk의 meta에 “doc_level”을 “child”로 지정하고 “parent_doc_id”로 parent chunk의 document id를 지정합니다. \n\n```python                \n        child_docs = []\n        for i, doc in enumerate(parent_docs):\n            _id = parent_doc_ids[i]\n            sub_docs = child_splitter.split_documents([doc])\n            for _doc in sub_docs:\n                _doc.metadata[\"parent_doc_id\"] = _id\n                _doc.metadata[\"doc_level\"] = \"child\"\n            child_docs.extend(sub_docs)\n                \n        child_doc_ids = vectorstore.add_documents(child_docs, bulk_size = 10000)\n                    \n        ids = parent_doc_ids+child_doc_ids\n```\n\nOpenSearch에 RAG 정보를 요청할 때에 아래와 같이 pre_filter로 doc_level이 child인 문서들을 검색합니다. \n\n```python\ndef get_documents_from_opensearch(vectorstore_opensearch, query, top_k):\n    result = vectorstore_opensearch.similarity_search_with_score(\n        query = query,\n        k = top_k*2,  \n        pre_filter={\"doc_level\": {\"$eq\": \"child\"}}\n    )\n            \n    relevant_documents = []\n    docList = []\n    for re in result:\n        if 'parent_doc_id' in re[0].metadata:\n            parent_doc_id = re[0].metadata['parent_doc_id']\n            doc_level = re[0].metadata['doc_level']\n```\n\nChild chunk의 parent_doc_id가 중복이 아닌 경우만 relevant_document로 활용합니다. \n\n```python\n      \n            if doc_level == 'child':\n                if parent_doc_id in docList:\n                    print('duplicated!')\n                else:\n                    relevant_documents.append(re)\n                    docList.append(parent_doc_id)\n                    \n                    if len(relevant_documents)\u003e=top_k:\n                        break\n                                \nreturn relevant_documents\n```\n\nOpenSearch에서 parent doc의 가져와서 RAG에서 활용합니다.\n\n```python\nrelevant_documents = get_documents_from_opensearch(vectorstore_opensearch, keyword, top_k)\n\nfor i, document in enumerate(relevant_documents):\n    parent_doc_id = document[0].metadata['parent_doc_id']\n    doc_level = document[0].metadata['doc_level']        \n    excerpt, uri = get_parent_document(parent_doc_id) # use pareant document\n\ndef get_parent_document(parent_doc_id):\n    response = os_client.get(\n        index=\"idx-rag\", \n        id = parent_doc_id\n    )\n    \n    source = response['_source']                                \n    metadata = source['metadata']    \n    return source['text'], metadata['uri']\n```\n\nMeta 파일을 생성하면 문서 업데이트나 삭제시 유용하게 사용할 수 있습니다.\n\n```python\ndef create_metadata(bucket, key, meta_prefix, s3_prefix, uri, category, documentId, ids):\n    title = key\n    timestamp = int(time.time())\n\n    metadata = {\n        \"Attributes\": {\n            \"_category\": category,\n            \"_source_uri\": uri,\n            \"_version\": str(timestamp),\n            \"_language_code\": \"ko\"\n        },\n        \"Title\": title,\n        \"DocumentId\": documentId,      \n        \"ids\": ids  \n    }\n    \n    objectName = (key[key.find(s3_prefix)+len(s3_prefix)+1:len(key)])\n\n    client = boto3.client('s3')\n    try: \n        client.put_object(\n            Body=json.dumps(metadata), \n            Bucket=bucket, \n            Key=meta_prefix+objectName+'.metadata.json' \n        )\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)        \n        raise Exception (\"Not able to create meta file\")\n```\n\n문서를 삭제하거나 업데이트 할 때에 OpenSearch의 문서를 삭제합니다. \n\n```python\ndef delete_document_if_exist(metadata_key):\n    try: \n        s3r = boto3.resource(\"s3\")\n        bucket = s3r.Bucket(s3_bucket)\n        objs = list(bucket.objects.filter(Prefix=metadata_key))\n        \n        if(len(objs)\u003e0):\n            doc = s3r.Object(s3_bucket, metadata_key)\n            meta = doc.get()['Body'].read().decode('utf-8')\n            \n            ids = json.loads(meta)['ids']\n\n            result = vectorstore.delete(ids) \n        else:\n            print('no meta file: ', metadata_key)\n```\n\n### RAG의 파일 업로드\n\nS3에 Object 업로드시 발생하는 이벤트 형태에는 OBJECT_CREATED_PUT (일반파일), CREATED_COMPLETE_MULTIPART_UPLOAD (대용량 파일)이 있습니다.\n\n```python\nconst s3PutEventSource = new lambdaEventSources.S3EventSource(s3Bucket, {\n    events: [\n      s3.EventType.OBJECT_CREATED_PUT,\n      s3.EventType.OBJECT_REMOVED_DELETE,\n      s3.EventType.OBJECT_CREATED_COMPLETE_MULTIPART_UPLOAD\n    ],\n    filters: [\n      { prefix: s3_prefix+'/' },\n    ]\n  });\n  lambdaS3eventManager.addEventSource(s3PutEventSource);\n```\n\n### RAG의 결과를 신뢰도에 따라 정렬하기\n\nFAISS를 이용해 일정 신뢰도 이상만을 관련된 문서로 활용합니다. \n\n```python\nif len(relevant_docs) \u003e= 1:\n    selected_relevant_docs = priority_search(revised_question, relevant_docs, bedrock_embeddings)\n\ndef priority_search(query, relevant_docs, bedrock_embeddings):\n    excerpts = []\n    for i, doc in enumerate(relevant_docs):\n        excerpts.append(\n            Document(\n                page_content=doc['metadata']['excerpt'],\n                metadata={\n                    'name': doc['metadata']['title'],\n                    'order':i,\n                }\n            )\n        )  \n\n    embeddings = bedrock_embeddings\n    vectorstore_confidence = FAISS.from_documents(\n        excerpts,  # documents\n        embeddings  # embeddings\n    )            \n    rel_documents = \n        vectorstore_confidence.similarity_search_with_score(\n             query=query,\n             k=top_k\n        )\n    docs = []\n    for i, document in enumerate(rel_documents):\n        order = document[0].metadata['order']\n        name = document[0].metadata['name']\n        assessed_score = document[1]\n\n        relevant_docs[order]['assessed_score'] = int(assessed_score)\n\n        if assessed_score \u003c 200:\n            docs.append(relevant_docs[order])    \n    return docs\n```\n\n### LangChain Agent\n\nChatBedrock의 Llama3를 지원하고 있지만 Agent는 아직 지원하고 있지 않습니다. 관련 에러는 아래와 같습니다. \n\n```text\nfor chunk in self._prepare_input_and_invoke_stream(\nFile \"/var/lang/lib/python3.11/site-packages/langchain_aws/llms/bedrock.py\", line 756, in _prepare_input_and_invoke_stream\nraise ValueError(\nValueError: Stop sequence key name for meta is not supported.\n```\n\n관련 이슈는 아래와 같습니다.\n\n[Stop sequence key name for meta is not supported](https://github.com/langchain-ai/langchain/issues/19220)\n\n[Error : Stop sequence key name for {meta or mistral or any other mode} is not supported](https://github.com/langchain-ai/langchain/issues/20053)\n\n## 직접 실습 해보기\n\n### 사전 준비 사항\n\n이 솔루션을 사용하기 위해서는 사전에 아래와 같은 준비가 되어야 합니다.\n\n- [AWS Account 생성](https://repost.aws/ko/knowledge-center/create-and-activate-aws-account)\n\n### CDK를 이용한 인프라 설치\n\n본 실습에서는 Oregon 리전 (us-west-2)을 사용합니다. [인프라 설치](./deployment.md)에 따라 CDK로 인프라 설치를 진행합니다. \n\n### 실행 결과\n\n#### 기본 채팅\n\n메뉴에서 \"General Conversation\"을 선택하고 먼저 \"나는 여행을 좋아해\"라고 입력한 후에, 다시 아래처럼 \"제주\"라고 입력합니다. 대화 이력을 활용하였기 때문에 \"제주\"이라는 질문에 제주 여행과 관련된 대화를 수행합니다.  \n\n![image](https://github.com/user-attachments/assets/8d0cd216-11e8-4d79-af62-c925808584e5)\n\n\n브라우저에서 뒤로가기를 선택하여 아래와 같이 Conversation Type을 \"4. Translation\"로 선택합니다. \n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/231916ba-b1e7-41ec-a8a1-dd832629b943)\n\n이후, \"경주는 우리나라의 역사적인 도시입니다. 경주는 신라왕조의 수도였으며, 많은 문화유산을 가지고 있습니다. 경주에는 다양한 관광지가 있습니다. 불국사는 유네스코 세계문화유산으로 지정된 사찰입니다. 이 곳에는 많은 문화재가 있습니다. 둘째, 석굴암은 불국사와 함께 유네스코 세계문화유산으로 지정된 석굴입니다. 이 곳에는 많은 불상이 있습니다. 셋째, 경주의 역사적인 문화유산을 느낄 수 있는 곳입니다. 이 곳에는 안압지, 첨성대, 황룡사지 등이 있습니다. 넷째, 양동마을은 전통한옥마을로 옛날의 모습을 그대로 간직하고 있습니다. 경주에는 역사적인 문화유산이 많아 역사에 관심이 있는 분들에게 추천합니다. 또한, 경주는 자연경관도 아름답습니다. 경주를 방문하여 다양한 경험을 하실 수 있습니다. \"라고 입력합니다. 이때의 번역 결과는 아래와 같습니다.\n\n![image](https://github.com/user-attachments/assets/dd1063a5-6d57-4754-9d99-21aee0d92254)\n\n반대로 영어를 한국어로 번역할 수 있는지 확인하기 위하여, \"Gyeongju is a historic city in our country. It was the capital of the Silla Kingdom and has many cultural heritages. Gyeongju has various tourist attractions. Bulguksa Temple is a UNESCO World Cultural Heritage site and has many cultural assets. This place has many Buddha statues. Second, Seokguram Grotto is a UNESCO World Cultural Heritage site along with Bulguksa Temple and has many Buddha statues. Third, it is a place where you can feel Gyeongju's historical cultural heritage. This place has Anapji Pond, Cheomseongdae Observatory, and Hwangnyongsa Temple, among others. Fourth, Yangdong Folk Village is a traditional Korean village that has preserved its old appearance. Gyeongju is recommended for those interested in history because it has many historical cultural heritages. Additionally, Gyeongju's natural scenery is also beautiful. You can have various experiences by visiting Gyeongju.\"로 입력합니다. \n\n\u003cimg width=\"876\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7a0bf9b5-0a11-41ed-ba9e-36b965bdd058\"\u003e\n\n\n\n메뉴에서 \"5. Grammatical Error Correction\"을 선택합니다. 이후 \"Gyeongju are a historic city in our country. It were the capital of the Silla Kingdom and have many cultural heritages.\"로 입력후 결과를 확인합니다. 아래와 같이 잘못된 문법과 수정된 내용을 보여줍니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/9b22c400-5776-4ed5-b1cb-c551338fe053)\n\n\n\n이제 RAG를 시험하기 위하여, 메뉴에서 아래처럼 \"3. RAG-opensearch (hybrid)\"을 선택합니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/b2daa766-a9f8-4b79-8077-a14c58e7f0f9)\n\n[error_code.pdf](./contents/error_code.pdf)을 다운로드 한 후에, 채팅창의 파일 아이콘을 선택하여 업로드를 하면 아래와 같이 파일 내용을 요약한 결과를 확인할 수 있습니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/5974492a-d57b-4189-bd25-7fbf7fc5b243)\n\n이제, 아래와 같이 \"보일러 에러 코드에 대해 상세히 설명해줘.\"라고 입력한 후에 결과를 확인합니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/bd740367-2d61-4d8c-9a16-6c436445a793)\n\n결과의 아래쪽을 보면, 아래와 같이 OpenSearch의 Vector/Keyword 검색을 통해 결과가 얻어졌음을 알수 있습니다. \n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/5ab71703-a6a8-4dfd-b406-bfa719e58259)\n\n\n[ReAct-SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS](https://arxiv.org/pdf/2210.03629)을 다운로드 한 후에 파일 아이콘을 선택하여 업로드하면 아래와 같이 요약 결과를 보여줍니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/3b1c92f7-80cd-41be-af25-c7c1a47b79f9)\n\n이제, \"Tell me about KNOWLEDGE-INTENSIVE REASONING TASKS\"을 입력하면 아래와 같이 간단히 설명해줍니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/bbcfa84a-86ff-4cdf-a298-59adbaed0207)\n\n좀더 자세히 알기 위하여, 아래처럼 추가 질문을 하면 상세한 정보를 얻을 수 있습니다.\n\n![image](https://github.com/kyopark2014/llama3-rag-workshop/assets/52392004/cc7eb464-a133-41e8-9e6d-a5c11467d022)\n\n\n\n## 리소스 정리하기 \n\n더이상 인프라를 사용하지 않는 경우에 아래처럼 모든 리소스를 삭제할 수 있습니다. \n\n1) [API Gateway Console](https://us-west-2.console.aws.amazon.com/apigateway/main/apis?region=us-west-2)로 접속하여 \"api-chatbot-for-llama3-rag-workshop\", \"api-llama3-rag-workshop\"을 삭제합니다.\n\n2) [Cloud9 console](https://us-west-2.console.aws.amazon.com/cloud9control/home?region=us-west-2#/)에 접속하여 아래의 명령어로 전체 삭제를 합니다.\n\n```text\ncd ~/environment/llama3.1-rag-bot/cdk-llama3-rag-workshop/ \u0026\u0026 cdk destroy --all\n```\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyopark2014%2Fllama3.2-rag-bot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyopark2014%2Fllama3.2-rag-bot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyopark2014%2Fllama3.2-rag-bot/lists"}