{"id":25009942,"url":"https://github.com/kyopark2014/managed-rag","last_synced_at":"2025-08-29T09:24:27.791Z","repository":{"id":254508637,"uuid":"846753981","full_name":"kyopark2014/managed-rag","owner":"kyopark2014","description":"The project shows a managed RAG service based on Knowledge Base.","archived":false,"fork":false,"pushed_at":"2024-12-27T14:31:41.000Z","size":2862,"stargazers_count":4,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T19:39:15.576Z","etag":null,"topics":["aws","knowledge-base","opensearch","rag","serverless-architectures"],"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,"zenodo":null}},"created_at":"2024-08-23T22:10:32.000Z","updated_at":"2024-12-27T14:31:44.000Z","dependencies_parsed_at":"2024-08-23T23:28:38.227Z","dependency_job_id":"f6ec415d-1719-463c-a302-bcbcfbe6e54b","html_url":"https://github.com/kyopark2014/managed-rag","commit_stats":null,"previous_names":["kyopark2014/managed-rag"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kyopark2014/managed-rag","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fmanaged-rag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fmanaged-rag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fmanaged-rag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fmanaged-rag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyopark2014","download_url":"https://codeload.github.com/kyopark2014/managed-rag/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyopark2014%2Fmanaged-rag/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272659284,"owners_count":24971607,"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","status":"online","status_checked_at":"2025-08-29T02:00:10.610Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["aws","knowledge-base","opensearch","rag","serverless-architectures"],"created_at":"2025-02-05T04:52:13.149Z","updated_at":"2025-08-29T09:24:27.762Z","avatar_url":"https://github.com/kyopark2014.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 완전관리형 RAG 구성하기\n\n\u003cp align=\"left\"\u003e\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%2Fmanaged-rag\u0026count_bg=%2379C83D\u0026title_bg=%23555555\u0026icon=\u0026icon_color=%23E7E7E7\u0026title=hits\u0026edge_flat=false)](https://hits.seeyoufarm.com\"/\u003e\u003c/a\u003e\n    \u003cimg alt=\"License\" src=\"https://img.shields.io/badge/LICENSE-MIT-green\"\u003e\n\u003c/p\u003e\n\n\n여기에서는 완전관리형 RAG(Fully Managed RAG)를 이용하여 편리하게 RAG를 구성하는 방법을 설명합니다. 전체적인 architecture는 아래와 같습니다. 여기에서는 변화하는 트래픽을 쉽게 관리하고 및 유지보수등이 용이한 serverless architecture를 이용합니다. 지식 저장소(knowledge store)로는 OpenSearch serverless를 활용하는 Amazon Bedrock Knowledge Base를 이용합니다. \n\n![image](https://github.com/user-attachments/assets/b3a93dc6-110b-4cce-aad1-d8a92f957d93)\n\n\n## 구현 주요 내용\n\n### OpenSearch 생성\n\nKnowledge base를 사용하기 위해서는 serverless opensearch를 사용하여야 합니다. [cdk-managed-rag-stack.ts](./cdk-managed-rag/lib/cdk-managed-rag-stack.ts)에서는 아래와 같이 serverless opensearch를 생성합니다.\n\n먼저 knowledge base를 위한 Role을 생성합니다.\n```typescript\nconst knowledge_base_role = new iam.Role(this,  `role-knowledge-base-for-${projectName}`, {\n    roleName: `role-knowledge-base-for-${projectName}-${region}`,\n    assumedBy: new iam.CompositePrincipal(\n        new iam.ServicePrincipal(\"bedrock.amazonaws.com\")\n    )\n});\n      \nconst bedrockInvokePolicy = new iam.PolicyStatement({ \n    effect: iam.Effect.ALLOW,\n    resources: [\n        `arn:aws:bedrock:${region}::foundation-model/anthropic.claude-3-haiku-20240307-v1:0`,\n        `arn:aws:bedrock:${region}::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0`,\n        `arn:aws:bedrock:${region}::foundation-model/amazon.titan-embed-text-v1`,\n        `arn:aws:bedrock:${region}::foundation-model/amazon.titan-embed-text-v2:0`\n    ],\n    actions: [\n        \"bedrock:InvokeModel\", \n        \"bedrock:InvokeModelEndpoint\", \n        \"bedrock:InvokeModelEndpointAsync\",        \n    ],\n});        \n\nknowledge_base_role.attachInlinePolicy( \n    new iam.Policy(this, `bedrock-invoke-policy-for-${projectName}`, {\n        statements: [bedrockInvokePolicy],\n    }),\n);  \n      \nconst bedrockKnowledgeBaseS3Policy = new iam.PolicyStatement({\n    effect: iam.Effect.ALLOW,\n    resources: ['*'],\n    actions: [\n        \"s3:GetBucketLocation\",\n        \"s3:GetObject\",\n        \"s3:ListBucket\",\n        \"s3:ListBucketMultipartUploads\",\n        \"s3:ListMultipartUploadParts\",\n        \"s3:AbortMultipartUpload\",\n        \"s3:CreateBucket\",\n        \"s3:PutObject\",\n        \"s3:PutBucketLogging\",\n        \"s3:PutBucketVersioning\",\n        \"s3:PutBucketNotification\",\n    ],\n});\n\nknowledge_base_role.attachInlinePolicy( \n    new iam.Policy(this, `knowledge-base-s3-policy-for-${projectName}`, {\n        statements: [bedrockKnowledgeBaseS3Policy],\n    }),\n);  \n      \nconst knowledgeBaseOpenSearchPolicy = new iam.PolicyStatement({\n    effect: iam.Effect.ALLOW,\n    resources: ['*'],\n    actions: [\"aoss:APIAccessAll\"],\n    });\n    knowledge_base_role.attachInlinePolicy( \n    new iam.Policy(this, `bedrock-agent-opensearch-policy-for-${projectName}`, {\n        statements: [knowledgeBaseOpenSearchPolicy],\n    }),\n);  \n  \nconst knowledgeBaseBedrockPolicy = new iam.PolicyStatement({\n    effect: iam.Effect.ALLOW,\n    resources: ['*'],\n    actions: [\"bedrock:*\"],\n});\n    \nknowledge_base_role.attachInlinePolicy( \n    new iam.Policy(this, `bedrock-agent-bedrock-policy-for-${projectName}`, {\n        statements: [knowledgeBaseBedrockPolicy],\n    }),\n);  \n```\n\nServerless OpenSearch를 생성합니다. \n```typescript\nconst collectionName = projectName\nconst OpenSearchCollection = new opensearchserverless.CfnCollection(this, `opensearch-correction-for-${projectName}`, {\n    name: collectionName,    \n    description: `opensearch correction for ${projectName}`,\n    standbyReplicas: 'DISABLED',\n    type: 'VECTORSEARCH',\n});\n      \nconst collectionArn = OpenSearchCollection.attrArn\nconst opensearch_url = OpenSearchCollection.attrCollectionEndpoint\nconst encPolicyName = `encription-${projectName}`\nconst encPolicy = new opensearchserverless.CfnSecurityPolicy(this, `opensearch-encription-security-policy`, {\n    name: encPolicyName,\n    type: \"encryption\",\n    description: `opensearch encryption policy for ${projectName}`,\n    policy:\n        '{\"Rules\":[{\"ResourceType\":\"collection\",\"Resource\":[\"collection/*\"]}],\"AWSOwnedKey\":true}',      \n});\nOpenSearchCollection.addDependency(encPolicy);\n\nconst netPolicyName = `network-${projectName}`\nconst netPolicy = new opensearchserverless.CfnSecurityPolicy(this, `opensearch-network-security-policy`, {\n    name: netPolicyName,\n    type: 'network',    \n    description: `opensearch network policy for ${projectName}`,\n    policy: JSON.stringify([\n        {\n            Rules: [\n                {\n                    ResourceType: \"collection\",\n                    Resource: [\"collection/*\"],              \n                }\n            ],\n            AllowFromPublic: true,          \n        },\n    ]),         \n});\nOpenSearchCollection.addDependency(netPolicy);\n\nconst dataAccessPolicyName = `data-${projectName}`\nconst dataAccessPolicy = new opensearchserverless.CfnAccessPolicy(this, `opensearch-data-collection-policy-for-${projectName}`, {\n    name: dataAccessPolicyName,\n    type: \"data\",\n    policy: JSON.stringify([\n        {\n            Rules: [\n              {\n                Resource: [`collection/${collectionName}`],\n                Permission: [\n                  \"aoss:CreateCollectionItems\",\n                  \"aoss:DeleteCollectionItems\",\n                  \"aoss:UpdateCollectionItems\",\n                  \"aoss:DescribeCollectionItems\",\n                ],\n                ResourceType: \"collection\",\n              },\n              {\n                Resource: [`index/${collectionName}/*`],\n                Permission: [\n                  \"aoss:CreateIndex\",\n                  \"aoss:DeleteIndex\",\n                  \"aoss:UpdateIndex\",\n                  \"aoss:DescribeIndex\",\n                  \"aoss:ReadDocument\",\n                  \"aoss:WriteDocument\",\n                ], \n                ResourceType: \"index\",\n              }\n            ],\n            Principal: [\n              `arn:aws:iam::${accountId}:role/${knowledge_base_role.roleName}`,\n              `arn:aws:iam::${accountId}:role/role-lambda-chat-ws-for-${projectName}-${region}`,\n              //`arn:aws:iam::${accountId}:role/administration`,\n              `arn:aws:sts::${accountId}:assumed-role/administration/ksdyb-Isengard`, \n            ], \n        },\n    ]),\n});\nOpenSearchCollection.addDependency(dataAccessPolicy);\n```  \n\n### OpenSearch Index 생성\n\nOpenSearch를 위한 index를 생성합니다. 상세한 내용은 [lambda_function.py](./lambda-chat-ws/lambda_function.py)를 참조합니다. \n\n생성하려는 vector index의 이름으로 이미 동일한 이름의 index가 있는지 확인합니다.\n\n```python\nfrom opensearchpy import OpenSearch, RequestsHttpConnection, AWSV4SignerAuth\n\nregion = os.environ.get('AWS_REGION', 'us-west-2')\nservice = \"aoss\"  \ncredentials = boto3.Session().get_credentials()\nawsauth = AWSV4SignerAuth(credentials, region, service)\n\nos_client = OpenSearch(\n    hosts = [{\n        'host': opensearch_url.replace(\"https://\", \"\"), \n        'port': 443\n    }],\n    http_auth=awsauth,\n    use_ssl = True,\n    verify_certs = True,\n    connection_class=RequestsHttpConnection,\n)\n\ndef is_not_exist(index_name):    \n    if os_client.indices.exists(index_name):\n        print('use exist index: ', index_name)    \n        return False\n    else:\n        print('no index: ', index_name)\n        return True\n```\n\n기존에 opensearch index가 없다면 아래와 같이 생성합니다.\n\n```python\nif(is_not_exist(vectorIndexName)):\n    body={\n        'settings':{\n            \"index.knn\": True,\n            \"index.knn.algo_param.ef_search\": 512,\n        },\n        'mappings': {\n            'properties': {\n                'vector_field': {\n                    'type': 'knn_vector',\n                    'dimension': 1024,\n                    'method': {\n                        \"name\": \"hnsw\",\n                        \"engine\": \"faiss\",\n                        \"parameters\": {\n                            \"ef_construction\": 512,\n                            \"m\": 16\n                        }\n                    }                  \n                },\n                \"AMAZON_BEDROCK_METADATA\": {\"type\": \"text\", \"index\": False},\n                \"AMAZON_BEDROCK_TEXT_CHUNK\": {\"type\": \"text\"},\n            }\n        }\n    }\n            \n    try: # create index\n        response = os_client.indices.create(\n            vectorIndexName,\n            body=body\n        )\n        print('opensearch index was created:', response)\n\n        # delay 3seconds\n        time.sleep(5)\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)                \n```\n\nKnowledge base가 이미 생성되어 있는지 확인하기 위하여 boto3의 [list_knowledge_bases](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/list_knowledge_bases.html)으로 현재의 knowledge base의 리스트를 확인합니다. \n            \n```python\nknowledge_base_name = projectName\ntry: \n    client = boto3.client('bedrock-agent')         \n    response = client.list_knowledge_bases(\n        maxResults=10\n    )\n        \n    if \"knowledgeBaseSummaries\" in response:\n        summaries = response[\"knowledgeBaseSummaries\"]\n        for summary in summaries:\n            if summary[\"name\"] == knowledge_base_name:\n                knowledge_base_id = summary[\"knowledgeBaseId\"]\n                print('knowledge_base_id: ', knowledge_base_id)\nexcept Exception:\n    err_msg = traceback.format_exc()\n    print('error message: ', err_msg)\n```\n\n### Knowledge Base 생성\n\nOpenSearch index 생성하는 동안에 바로 knowledge base를 생성하게 되면 관련 정보를 가져올 수 있으므로 delay를 두고 재시도 합니다. Knowledge Base의 설정은 [KnowledgeBaseConfiguration](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_KnowledgeBaseConfiguration.html)을 참조합니다. 여기서 type은 \"VECTOR\"을 저정하도록 되어 있습니다. \n\n```python\nif not knowledge_base_id:\n    print('creating knowledge base...')        \n    for atempt in range(3):\n        try:\n            response = client.create_knowledge_base(\n                name=knowledge_base_name,\n                description=\"Knowledge base based on OpenSearch\",\n                roleArn=knowledge_base_role,\n                knowledgeBaseConfiguration={\n                    'type': 'VECTOR',\n                    'vectorKnowledgeBaseConfiguration': {\n                        'embeddingModelArn': embeddingModelArn,\n                        'embeddingModelConfiguration': {\n                            'bedrockEmbeddingModelConfiguration': {\n                                'dimensions': 1024\n                            }\n                        }\n                    }\n                },\n                storageConfiguration={\n                    'type': 'OPENSEARCH_SERVERLESS',\n                    'opensearchServerlessConfiguration': {\n                        'collectionArn': collectionArn,\n                        'fieldMapping': {\n                            'metadataField': 'AMAZON_BEDROCK_METADATA',\n                            'textField': 'AMAZON_BEDROCK_TEXT_CHUNK',\n                            'vectorField': 'vector_field'\n                        },\n                        'vectorIndexName': vectorIndexName\n                    }\n                }                \n            )   \n            print('(create_knowledge_base) response: ', response)\n            \n            if 'knowledgeBaseId' in response['knowledgeBase']:\n                knowledge_base_id = response['knowledgeBase']['knowledgeBaseId']\n                break\n            else:\n                knowledge_base_id = \"\"    \n        except Exception:\n                err_msg = traceback.format_exc()\n                print('error message: ', err_msg)\n                time.sleep(5)\n                print(f\"retrying... ({atempt})\")\n```\n\n### 데이터 소스 생성\n\nAmazon S3를 바로보는 data source를 사용하고자 합니다. 먼저 data source가 이미 생성되어 있는지 [list_data_sources](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/quicksight/client/list_data_sources.html)으로 확인합니다.\n\n```python                \ndata_source_name = s3_bucket  \ntry: \n    response = client.list_data_sources(\n        knowledgeBaseId=knowledge_base_id,\n        maxResults=10\n    )        \n        \n    if 'dataSourceSummaries' in response:\n        for data_source in response['dataSourceSummaries']:\n            if data_source['name'] == data_source_name:\n                data_source_id = data_source['dataSourceId']\n                break    \nexcept Exception:\n    err_msg = traceback.format_exc()\n    print('error message: ', err_msg)\n```\n\n새로 data source를 생성합니다.\n\n```python        \nif not data_source_id:\n    print('creating data source...')  \n    try:\n        response = client.create_data_source(\n            dataDeletionPolicy='DELETE',\n            dataSourceConfiguration={\n                's3Configuration': {\n                    'bucketArn': s3_arn,\n                    'inclusionPrefixes': [ \n                        s3_prefix+'/',\n                    ]\n                },\n                'type': 'S3'\n            },\n            description = f\"S3 data source: {s3_bucket}\",\n            knowledgeBaseId = knowledge_base_id,\n            name = data_source_name,\n            vectorIngestionConfiguration={\n                'chunkingConfiguration': {\n                    'chunkingStrategy': 'HIERARCHICAL',\n                    'hierarchicalChunkingConfiguration': {\n                        'levelConfigurations': [\n                            {\n                                'maxTokens': 1500\n                            },\n                            {\n                                'maxTokens': 300\n                            }\n                        ],\n                        'overlapTokens': 60\n                    }\n                },\n                'parsingConfiguration': {\n                    'bedrockFoundationModelConfiguration': {\n                        'modelArn': parsingModelArn\n                    },\n                    'parsingStrategy': 'BEDROCK_FOUNDATION_MODEL'\n                }\n            }\n        )\n        print('(create_data_source) response: ', response)\n            \n        if 'dataSource' in response:\n            if 'dataSourceId' in response['dataSource']:\n                data_source_id = response['dataSource']['dataSourceId']\n                print('data_source_id: ', data_source_id)\n                    \n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)\n```            \n\n### 데이터 소스 동기화\n\nBoto3의 [start_ingestion_job](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/start_ingestion_job.html)을 이용하여 데이터 동기화를 요청합니다.\n\n여기에서는 사용자가 파일이 올릴때에 동기화를 요청합니다. 대량으로 파일들을 동기화할 경우에는 Amazon S3에 파일을 업로드하고 knowledge base에서 수동으로 동기화를 하거나 event bridge를 이용해 정기적으로 동기화를 수행합니다. \n\n```python\nif knowledge_base_id and data_source_id:\n    try:\n        client = boto3.client('bedrock-agent')\n        response = client.start_ingestion_job(\n            knowledgeBaseId=knowledge_base_id,\n            dataSourceId=data_source_id\n        )\n    except Exception:\n        err_msg = traceback.format_exc()\n        print('error message: ', err_msg)\n```\n\n\n### Knowledge Base에서 관련된 문서 가져오기\n\nKnowledbe base에서 관련된 문서를 조회하기 위해서 [AmazonKnowledgeBasesRetriever](https://python.langchain.com/v0.2/api_reference/aws/retrievers/langchain_aws.retrievers.bedrock.AmazonKnowledgeBasesRetriever.html)을 이용합니다.\n\n```python\nfrom langchain_aws import AmazonKnowledgeBasesRetriever\n\nknowledge_base_id = None\nknowledge_base_name = projectName \ndef retrieve_from_knowledge_base(query):\n    global knowledge_base_id\n    if not knowledge_base_id:        \n        client = boto3.client('bedrock-agent')         \n        response = client.list_knowledge_bases(\n            maxResults=10\n        )\n        print('response: ', response)\n                \n        if \"knowledgeBaseSummaries\" in response:\n            summaries = response[\"knowledgeBaseSummaries\"]\n            for summary in summaries:\n                if summary[\"name\"] == knowledge_base_name:\n                    knowledge_base_id = summary[\"knowledgeBaseId\"]\n                    print('knowledge_base_id: ', knowledge_base_id)\n                    break\n    \n    relevant_docs = []\n    if knowledge_base_id:    \n        retriever = AmazonKnowledgeBasesRetriever(\n            knowledge_base_id=knowledge_base_id, \n            retrieval_config={\"vectorSearchConfiguration\": {\"numberOfResults\": 2}},\n        )\n        \n        relevant_docs = retriever.invoke(query)\n        print(relevant_docs)\n    \n    docs = []\n    for i, document in enumerate(relevant_docs):\n        #print(f\"{i}: {document.page_content}\")\n        print_doc(i, document)\n        if document.page_content:\n            excerpt = document.page_content\n        \n        score = document.metadata[\"score\"]\n        # print('score:', score)\n        doc_prefix = \"knowledge-base\"\n        \n        link = \"\"\n        if \"s3Location\" in document.metadata[\"location\"]:\n            link = document.metadata[\"location\"][\"s3Location\"][\"uri\"] if document.metadata[\"location\"][\"s3Location\"][\"uri\"] is not None else \"\"\n            \n            pos = link.find(f\"/{doc_prefix}\")\n            name = link[pos+len(doc_prefix)+1:]\n            encoded_name = parse.quote(name)\n            link = f\"{path}{doc_prefix}{encoded_name}\"\n            \n        elif \"webLocation\" in document.metadata[\"location\"]:\n            link = document.metadata[\"location\"][\"webLocation\"][\"url\"] if document.metadata[\"location\"][\"webLocation\"][\"url\"] is not None else \"\"\n            name = \"Web Crawler\"\n\n        docs.append(\n            Document(\n                page_content=excerpt,\n                metadata={\n                    'name': name,\n                    'url': link,\n                    'from': 'RAG'\n                },\n            )\n        )\n    return docs\n```\n\n### Knowlodge Base 조회하는 기능을 Tool로 등록하기\n\nKnowledge Base를 조회하는 함수를 Tool로 등록하여 agent에서 tool use 패턴으로 활용합니다. 이를 위해 아래와 같이 tool로 등록할때 검색조건을 \"Search technical information by keyword\"로 설정합니다. 이때, numberOfResults 수만큼 검색합니다.\n\n```python\n@tool    \ndef search_by_knowledge_base(keyword: str) -\u003e str:\n    \"\"\"\n    Search technical information by keyword and then return the result as a string.\n    keyword: search keyword\n    return: the technical information of keyword\n    \"\"\"    \n    print(\"###### search_by_knowledge_base ######\")\n    \n    top_k = 4\n    relevant_docs = []\n    if knowledge_base_id:    \n        retriever = AmazonKnowledgeBasesRetriever(\n            knowledge_base_id=knowledge_base_id, \n            retrieval_config={\"vectorSearchConfiguration\": {\n                \"numberOfResults\": top_k,\n                \"overrideSearchType\": \"HYBRID\"   # SEMANTIC\n            }},\n        )        \n        relevant_docs = retriever.invoke(keyword)\n        \n    filtered_docs = grade_documents(keyword, relevant_docs)\n    filtered_docs = check_duplication(filtered_docs)  # duplication checker\n            \n    relevant_context = \"\"\n    for i, document in enumerate(filtered_docs):\n        if document.page_content:\n            content = document.page_content            \n        relevant_context = relevant_context + content + \"\\n\\n\"        \n    \n    return relevant_context\n```\n\n### Agentic Workflow의 구현\n\n여기에서는 agectic workflow의 tool use 패턴을 이용하여 knowledge base로 구성한 RAG의 정보를 조회하여 활용합니다. 아래와 같이 Workflow를 정의합니다.\n\n```python\nclass State(TypedDict):\n    messages: Annotated[list, add_messages]\n\nworkflow = StateGraph(State)\n\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"action\", tool_node)\nworkflow.add_edge(START, \"agent\")\nworkflow.add_conditional_edges(\n    \"agent\",\n    should_continue,\n    {\n        \"continue\": \"action\",\n        \"end\": END,\n    },\n)\nworkflow.add_edge(\"action\", \"agent\")\n\napp = workflow.compile()\n```\n\n아래와 같이 실행합니다. \n\n```python\ninputs = [HumanMessage(content=query)]\nconfig = {\n    \"recursion_limit\": 50,\n    \"requestId\": requestId,\n    \"connectionId\": connectionId\n}\n\nfor event in app.stream({\"messages\": inputs}, config, stream_mode=\"values\"):   \n    message = event[\"messages\"][-1]\n```\n\n또한, search_by_knowledge_base을 포함한 tool들을 노드로 등록합니다.\n\n```python\ntools = [get_current_time, get_book_list, get_weather_info, search_by_tavily, search_by_knowledge_base]\n\nchatModel = get_chat()\nmodel = chatModel.bind_tools(tools)\n\ntool_node = ToolNode(tools)\n```\n\n이때의 agent의 call_model은 아래와 같습니다.\n\n```python\ndef call_model(state: State, config):\n    update_state_message(\"thinking...\", config)\n    \n    if isKorean(state[\"messages\"][0].content)==True:\n        system = (\n            \"질문에 친근한 방식으로 대답하도록 설계된 대화형 AI입니다.\"\n            \"상황에 맞는 구체적인 세부 정보를 충분히 제공합니다.\"\n            \"모르는 질문을 받으면 솔직히 모른다고 말합니다.\"\n        )\n    else: \n        system = (            \n            \"You are a conversational AI designed to answer in a friendly way to a question.\"\n            \"If you don't know the answer, just say that you don't know, don't try to make up an answer.\"\n            \"You will be acting as a thoughtful advisor.\"    \n        )\n        \n    prompt = ChatPromptTemplate.from_messages(\n        [\n            (\"system\", system),\n            MessagesPlaceholder(variable_name=\"messages\"),\n        ]\n    )\n    chain = prompt | model\n        \n    response = chain.invoke(state[\"messages\"])    \n    return {\"messages\": [response]}\n```\n\n또한, workflow의 condtional edge는 아래와 같이 정의합니다.\n\n```python\ndef should_continue(state: State) -\u003e Literal[\"continue\", \"end\"]:\n    messages = state[\"messages\"]    \n    \n    last_message = messages[-1]            \n    if not last_message.tool_calls:\n        next = \"end\"\n    else:           \n        next = \"continue\"         \n    return next\n```\n\n\n### 참고문헌 가져오기\n\n참고문헌은 document의 metafile에서 추출하여 아래와 같이 활용합니다. \n\n```python\ndef get_reference_of_knoweledge_base(docs, path, doc_prefix):\n    reference = \"\\n\\nFrom\\n\"\n    #print('path: ', path)\n    #print('doc_prefix: ', doc_prefix)\n    #print('prefix: ', f\"/{doc_prefix}\")\n    \n    for i, document in enumerate(docs):\n        if document.page_content:\n            excerpt = document.page_content\n        \n        score = document.metadata[\"score\"]\n        print('score:', score)\n        doc_prefix = \"knowledge-base\"\n        \n        link = \"\"\n        if \"s3Location\" in document.metadata[\"location\"]:\n            link = document.metadata[\"location\"][\"s3Location\"][\"uri\"] if document.metadata[\"location\"][\"s3Location\"][\"uri\"] is not None else \"\"\n            \n            print('link:', link)    \n            pos = link.find(f\"/{doc_prefix}\")\n            name = link[pos+len(doc_prefix)+1:]\n            encoded_name = parse.quote(name)\n            print('name:', name)\n            link = f\"{path}{doc_prefix}{encoded_name}\"\n            \n        elif \"webLocation\" in document.metadata[\"location\"]:\n            link = document.metadata[\"location\"][\"webLocation\"][\"url\"] if document.metadata[\"location\"][\"webLocation\"][\"url\"] is not None else \"\"\n            name = \"WWW\"\n\n        print('link:', link)\n                    \n        reference = reference + f\"{i+1}. \u003ca href={link} target=_blank\u003e{name}\u003c/a\u003e, \u003ca href=\\\"#\\\" onClick=\\\"alert(`{excerpt}`)\\\"\u003e관련문서\u003c/a\u003e\\n\"\n                    \n    return reference\n```\n\n### LLM으로 RAG Grading 활용하기\n\nLLM의 관련된 문서의 숫자와 길이가 적다면 문서의 순서가 크게 영향을 주지 않습니다. 여기에서는 LLM으로 간단히 grading함으로써 RAG의 성능을 향상시키는 방법을 사용하고 있습니다. [LLM으로 RAG Grading 활용하기](https://github.com/kyopark2014/korean-chatbot-using-amazon-bedrock/blob/main/RAG-grading.md)에서는 prompt와 structured output을 이용하는 방법을 설명합니다.\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본 실습에서는 us-west-2 리전을 사용합니다. [인프라 설치](./deployment.md)에 따라 CDK로 인프라 설치를 진행합니다. \n\n## 실행결과\n\n채팅 메뉴에서 \"RAG Knowledge Base\"를 선택한 후에 \"교보 다이렉트 보험에 대해 설명해주세요.\"라고 입력하면 아래와 같이 RAG를 통해 얻어진 정보와 관련 문서를 확인할 수 있습니다.\n\n![image](https://github.com/user-attachments/assets/ff287438-ca1d-4d52-a718-c1c67dac597f)\n\n\n## 결론\n\nKnowledge Base를 활용하여 RAG를 적용할 때에 데이터의 등록 및 삭제를 편리하게 할 수 있습니다. 여기에서는 knowledge base의 지식 저장소로 Serverless OpenSearch를 사용하고 있어서 인프라의 관리에 대한 노력을 줄이면서도 충분한 RAG 성능을 확보할 수 있습니다. 인프라를 효율적으로 관리하기 위하여 AWS CDK로 OpenSearch를 설치하고 index와 data source를 python code로 관리하는 방법을 설명하였습니다. \n\n\n## 리소스 정리하기 \n\n더이상 인프라를 사용하지 않는 경우에 아래처럼 모든 리소스를 삭제할 수 있습니다. \n\n1) [API Gateway Console](https://us-\bwest-2.console.aws.amazon.com/apigateway/main/apis?region=us-west-2)로 접속하여 \"api-chatbot-for-managed-rag-chatbot\", \"api-managed-rag-chatbot\"을 삭제합니다.\n\n2) [Cloud9 Console](https://us-west-2.console.aws.amazon.com/cloud9control/home?region=us-west-2#/)에 접속하여 아래의 명령어로 전체 삭제를 합니다.\n\n```text\ncd ~/environment/managed-rag/cdk-managed-rag/ \u0026\u0026 cdk destroy --all\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyopark2014%2Fmanaged-rag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyopark2014%2Fmanaged-rag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyopark2014%2Fmanaged-rag/lists"}