RAG(Retrieval-Augmented Generation)๋ฅผ ํ์ฉํ๋ฉด, LLM(Large Language Model)์ ๊ธฐ๋ฅ์ ๊ฐํํ์ฌ ๋ค์ํ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์์๋ RAG์ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ๋ค์ ๋ํด ์ค๋ช ํ๊ณ ์ด๋ฅผ ์ด์ฉํ์ฌ ๊ธฐ์ ๋๋ ๊ฐ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ํ์ฉํ ์ ์๋ ํ๊ตญ์ด Chatbot์ ๋ง๋ค๊ณ ์ ํฉ๋๋ค.
- Multimodal: ํ ์คํธ๋ฟ ์๋๋ผ ์ด๋ฏธ์ง์ ๋ํ ๋ถ์์ ํ ์ ์์ต๋๋ค.
- Multi-RAG: ๋ค์ํ ์ง์ ์ ์ฅ์(Knowledge Store)ํ์ฉํฉ๋๋ค.
- Multi-Region LLM: ์ฌ๋ฌ ๋ฆฌ์ ์ ์๋ LLM์ ๋์์ ํ์ฉํจ์ผ๋ก์จ ์ง๋ฌธํ ๋ต๋ณ๊น์ง์ ๋์์๊ฐ์ ๋จ์ถํ๊ณ , On-Demand ๋ฐฉ์์ ๋์ ์คํ ์์ ์ ํ์ ์ํํ ์ ์์ต๋๋ค.
- Agent: ์ธ๋ถ API๋ฅผ ํตํด ์ป์ด์ง ๊ฒฐ๊ณผ๋ฅผ ๋ํ์ ํ์ฉํฉ๋๋ค.
- ์ธํฐ๋ท ๊ฒ์: RAG์ ์ง์์ ์ฅ์์ ๊ด๋ จ๋ ๋ฌธ์๊ฐ ์๋ ๊ฒฝ์ฐ์ ์ธํฐ๋ท ๊ฒ์์ ํตํด ํ์ฉ๋๋ฅผ ๋์ ๋๋ค.
- ํ์ ๋์ ๊ฒ์: RAG์ ํ๊ตญ์ด์ ์์ด ๋ฌธ์๋ค์ด ํผ์ฌํ ๊ฒฝ์ฐ์ ํ๊ตญ์ด๋ก ์์ด ๋ฌธ์๋ฅผ ๊ฒ์ํ ์ ์์ต๋๋ค. ํ๊ตญ์ด๋ก ํ๊ตญ์ด, ์์ด ๋ฌธ์๋ฅผ ๋ชจ๋ ๊ฒ์ํ์ฌ RAG์ ์ฑ๋ฅ์ ํฅ์ ์ํฌ ์ ์์ต๋๋ค.
- Prioroty Search: ๊ฒ์๋ ๋ฌธ์๋ฅผ ๊ด๋ จ๋์ ๋ฐ๋ผ ์ ๋ ฌํ๋ฉด LLM์ ๊ฒฐ๊ณผ๊ฐ ํฅ์๋ฉ๋๋ค.
- Kendra ์ฑ๋ฅ ํฅ์: LangChain์์ Kendra์ FAQ๋ฅผ ํ์ฉํฉ๋๋ค.
- Vector/Keyword ๊ฒ์: Vector ๊ฒ์(Sementaic) ๋ฟ ์๋๋ผ, Lexical ๊ฒ์(Keyword)์ ํ์ฉํ์ฌ ๊ด๋ จ๋ ๋ฌธ์๋ฅผ ์ฐพ์ ํ์จ์ ๋์ ๋๋ค.
- Code Generation: ๊ธฐ์กด ์ฝ๋๋ฅผ ์ด์ฉํ์ฌ Python/Node.js ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ๊ตฌํํ ์ฝ๋๋ค์ LangChain์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค. ๋ํ, ์๋์ ๊ฐ์ Prompt Engineing ์์ ๋ฅผ ์ฌ์ฉํด ๋ณผ ์ ์์ต๋๋ค.
- ๋ฒ์ญ (translation): ์ ๋ ฅ๋ ๋ฌธ์ฅ์ ๋ฒ์ญํฉ๋๋ค.
- ๋ฌธ๋ฒ ์ค๋ฅ ์ถ์ถ (Grammatical Error Correction): ์์ด์ ๋ํ ๋ฌธ์ฅ ์๋ฌ๋ฅผ ์ค๋ช ํ๊ณ , ์์ ๋ ๋ฌธ์ฅ์ ๋ณด์ฌ์ค๋๋ค.
- ๋ฆฌ๋ทฐ ๋ถ์ (Extracted Topic and Sentiment): ์ ๋ ฅ๋ ๋ฆฌ๋ทฐ์ ์ฃผ์ ์ ๊ฐ์ (Sentiment)์ ์ถ์ถํฉ๋๋ค.
- ์ ๋ณด ์ถ์ถ (Information Extraction): ์ ๋ ฅ๋ ๋ฌธ์ฅ์์ email๊ณผ ๊ฐ์ ์ ๋ณด๋ฅผ ์ถ์ถํฉ๋๋ค.
- ๊ฐ์ธ ์ ๋ณด ์ญ์ (Removing PII): ์ ๋ ฅ๋ ๋ฌธ์ฅ์์ ๊ฐ์ธ์ ๋ณด(PII)๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค.
- ๋ณต์กํ ์ง๋ฌธ (Complex Question): step-by-step์ผ๋ก ๋ณต์กํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
- ์ด๋ฆฐ์ด์ ๋ํ (Child Conversation): ๋ํ์๋์ ๋ง๊ฒ ์ ์ ํ ์ดํ๋ ๋ต๋ณ์ ํ ์ ์์ต๋๋ค.
- ์๊ฐ์ ๋ณด ์ถ์ถ (Timestamp Extraction): ์ ๋ ฅ๋ ์ ๋ณด์์ ์๊ฐ์ ๋ณด(timestemp)๋ฅผ ์ถ์ถํฉ๋๋ค.
- ์์ ๋ก์ด ๋ํ (Free Conversation): ์น๊ตฌ์ฒ๋ผ ๋ฐ๋ง๋ก ๋ํํฉ๋๋ค.
์ ์ฒด์ ์ธ ์ํคํ ์ฒ๋ ์๋์ ๊ฐ์ต๋๋ค. ์ฌ์ฉ์์ ์ง๋ฌธ์ WebSocket์ ์ด์ฉํ์ฌ AWS Lambda์์ RAG์ LLM์ ์ด์ฉํ์ฌ ๋ต๋ณํฉ๋๋ค. ๋ํ ์ด๋ ฅ(chat history)๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ(Question)์ ์๋ก์ด ์ง๋ฌธ(Revised question)์ผ๋ก ์์ฑํฉ๋๋ค. ์๋ก์ด ์ง๋ฌธ์ผ๋ก ์ง์ ์ ์ฅ์(Knowledge Store)์ธ Kendra์ OpenSearch์ ํ์ฉํฉ๋๋ค. ๋๊ฐ์ ์ง์์ ์ฅ์์๋ ์ฉ๋์ ๋ง๋ ๋ฐ์ดํฐ๊ฐ ์ ๋ ฅ๋์ด ์๋๋ฐ, ๋ง์ฝ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ๊ฐ์ง๊ณ ์๋๋ผ๋, ๋๊ฐ์ ์ง์์ ์ฅ์์ ๋ฌธ์๋ฅผ ๊ฒ์ํ๋ ๋ฐฉ๋ฒ์ ์ฐจ์ด๋ก ์ธํด, ์๋ก ๋ณด์์ ์ธ ์ญํ ์ ํฉ๋๋ค. ์ง์์ ์ฅ์์ ํ๊ตญ์ด/ํ๊ตญ์ด๋ก ๋ ๋ฌธ์๋ค์ด ์๋ค๋ฉด, ํ๊ตญ์ด ์ง๋ฌธ์ ์์ด๋ก ๋ ๋ฌธ์๋ฅผ ๊ฒ์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ง๋ฌธ์ด ํ๊ตญ์ด๋ผ๋ฉด ํ๊ตญ์ด๋ก ํ๊ตญ์ด ๋ฌธ์๋ฅผ ๋จผ์ ๊ฒ์ํ ํ์, ์์ด๋ก ๋ฒ์ญํ์ฌ ๋ค์ ํ๋ฒ ์์ด ๋ฌธ์๋ค์ ๊ฒ์ํฉ๋๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ํ๊ตญ์ด๋ก ์ง๋ฌธ์ ํ๋๋ผ๋ ์์ด ๋ฌธ์๊น์ง ๊ฒ์ํ์ฌ ๋ ๋์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค. ๋ง์ฝ ๋ ์ง์์ ์ฅ์๊ฐ ๊ด๋ จ๋ ๋ฌธ์(Relevant documents)๋ฅผ ๊ฐ์ง๊ณ ์์ง ์๋ค๋ฉด, Google Search API๋ฅผ ์ด์ฉํ์ฌ ์ธํฐ๋ท์ ๊ด๋ จ๋ ์นํ์ด์ง๋ค์ด ์๋์ง ํ์ธํ๊ณ , ์ด๋ ์ป์ด์ง ๊ฒฐ๊ณผ๋ฅผ RAG์ฒ๋ผ ํ์ฉํฉ๋๋ค.
์์ธํ๊ฒ ๋จ๊ณ๋ณ๋ก ์ค๋ช ํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
๋จ๊ณ 1: ์ฌ์ฉ์์ ์ง๋ฌธ(question)์ API Gateway๋ฅผ ํตํด Lambda์ Web Socket ๋ฐฉ์์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. Lambda๋ JSON body์์ ์ง๋ฌธ์ ์ฝ์ด์ต๋๋ค. ์ด๋ ์ฌ์ฉ์์ ์ด์ ๋ํ์ด๋ ฅ์ด ํ์ํ๋ฏ๋ก Amazon DynamoDB์์ ์ฝ์ด์ต๋๋ค. DynamoDB์์ ๋ํ์ด๋ ฅ์ ๋ก๋ฉํ๋ ์์ ์ ์ฒ์ 1ํ๋ง ์ํํฉ๋๋ค.
๋จ๊ณ 2: ์ฌ์ฉ์์ ๋ํ์ด๋ ฅ์ ๋ฐ์ํ์ฌ ์ฌ์ฉ์์ Chatbot์ด interactiveํ ๋ํ๋ฅผ ํ ์ ์๋๋ก, ๋ํ์ด๋ ฅ๊ณผ ์ฌ์ฉ์์ ์ง๋ฌธ์ผ๋ก ์๋ก์ด ์ง๋ฌธ(Revised Question)์ ์์ฑํ์ฌ์ผ ํฉ๋๋ค. LLM์ ๋ํ์ด๋ ฅ(chat history)๋ฅผ Context๋ก ์ ๊ณตํ๊ณ ์ ์ ํ Prompt๋ฅผ ์ด์ฉํ๋ฉด ์๋ก์ด ์ง๋ฌธ์ ์์ฑํ ์ ์์ต๋๋ค.
๋จ๊ณ 3: ์๋ก์ด ์ง๋ฌธ(Revised question)์ผ๋ก OpenSearch์ ์ง๋ฌธ์ ํ์ฌ ๊ด๋ จ๋ ๋ฌธ์(Relevant Documents)๋ฅผ ์ป์ต๋๋ค.
๋จ๊ณ 4: ์ง๋ฌธ์ด ํ๊ตญ์ด์ธ ๊ฒฝ์ฐ์ ์์ด ๋ฌธ์๋ ๊ฒ์ํ ์ ์๋๋ก ์๋ก์ด ์ง๋ฌธ(Revised question)์ ์์ด๋ก ๋ฒ์ญํฉ๋๋ค.
๋จ๊ณ 5: ๋ฒ์ญ๋ ์๋ก์ด ์ง๋ฌธ(translated revised question)์ ์ด์ฉํ์ฌ Kendra์ OpenSearch์ ์ง๋ฌธํฉ๋๋ค.
๋จ๊ณ 6: ๋ฒ์ญ๋ ์ง๋ฌธ์ผ๋ก ์ป์ ๊ด๋ จ๋ ๋ฌธ์๊ฐ ์์ด ๋ฌธ์์ผ ๊ฒฝ์ฐ์, LLM์ ํตํด ๋ฒ์ญ์ ์ํํฉ๋๋ค. ๊ด๋ จ๋ ๋ฌธ์๊ฐ ์ฌ๋ฌ๊ฐ์ด๋ฏ๋ก Multi-Region์ LLM๋ค์ ํ์ฉํ์ฌ ์ง์ฐ์๊ฐ์ ์ต์ํ ํฉ๋๋ค.
๋จ๊ณ 7: ํ๊ตญ์ด ์ง๋ฌธ์ผ๋ก ์ป์ N๊ฐ์ ๊ด๋ จ๋ ๋ฌธ์์, ์์ด๋ก ๋ N๊ฐ์ ๊ด๋ จ๋ ๋ฌธ์์ ํฉ์ ์ต๋ 2xN๊ฐ์ ๋๋ค. ์ด ๋ฌธ์๋ฅผ ๊ฐ์ง๊ณ Context Window ํฌ๊ธฐ์ ๋ง๋๋ก ๋ฌธ์๋ฅผ ์ ํํฉ๋๋ค. ์ด๋ ๊ด๋ จ๋๊ฐ ๋์ ๋ฌธ์๊ฐ Context์ ์๋จ์ ๊ฐ๋๋ก ๋ฐฐ์นํฉ๋๋ค.
๋จ๊ณ 8: ๊ด๋ จ๋๊ฐ ์ผ์ ์ดํ์ธ ๋ฌธ์๋ ๋ฒ๋ฆฌ๋ฏ๋ก, ํ๊ฐ์ RAG์ ๋ฌธ์๋ ์ ํ๋์ง ์์ ์ ์์ต๋๋ค. ์ด๋์๋ Google Seach API๋ฅผ ํตํด ์ธํฐ๋ท ๊ฒ์์ ์ํํ๊ณ , ์ด๋ ์ป์ด์ง ๋ฌธ์๋ค์ Priority Search๋ฅผ ํ์ฌ ๊ด๋ จ๋๊ฐ ์ผ์ ์ด์์ ๊ฒฐ๊ณผ๋ฅผ RAG์์ ํ์ฉํฉ๋๋ค.
๋จ๊ณ 9: ์ ํ๋ ๊ด๋ จ๋ ๋ฌธ์๋ค(Selected relevant documents)๋ก Context๋ฅผ ์์ฑํ ํ์ ์๋ก์ด ์ง๋ฌธ(Revised question)๊ณผ ํจ๊ป LLM์ ์ ๋ฌํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
์ด๋์ Sequence diagram์ ์๋์ ๊ฐ์ต๋๋ค. ๋ง์ฝ RAG์์ ๊ด๋ จ๋ ๋ฌธ์๋ฅผ ์ฐพ์ง๋ชปํ ๊ฒฝ์ฐ์๋ Google Search API๋ฅผ ํตํด Query๋ฅผ ์ํํ์ฌ RAG์ฒ๋ผ ํ์ฉํฉ๋๋ค. ๋ํ์ด๋ ฅ์ ๊ฐ์ ธ์ค๊ธฐ ์ํ DynamoDB๋ ์ฒซ๋ฒ์งธ ์ง๋ฌธ์๋ง ํด๋น๋ฉ๋๋ค. ์ฌ๊ธฐ์๋ "us-east-1"๊ณผ "us-west-2"์ Bedrock์ ์ฌ์ฉํ๋ฏ๋ก, ์๋์ ๊ฐ์ด ์ง๋ฌธ๋ง๋ค ๋ค๋ฅธ Region์ Bedrock Claude LLM์ ์ฌ์ฉํฉ๋๋ค.
๋๋์ผ๋ก ํ์ผ ์ ๋ก๋ ๋๋ ์ญ์ ์๋ ์๋์ ๊ฐ์ Event driven๊ตฌ์กฐ๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด S3๋ก ๋๊ท๋ชจ๋ก ๋ฌธ์ ๋๋ ์ฝ๋๋ฅผ ๋ฃ์๋์ ์ ๋ณด์ ์ ์ถ์์ด RAG์ ์ง์์ ์ฅ์๋ฅผ ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค.
์ฌ๋ฌ๊ฐ์ RAG๋ฅผ ํ์ฉํ ๊ฒฝ์ฐ์ ์์ฒญํ ์๋ต๊น์ง์ ์ง์ฐ์๊ฐ์ด ์ฆ๊ฐํฉ๋๋ค. ๋ฐ๋ผ์ ๋ณ๋ ฌ ํ๋ก์ธ์ฑ์ ์ด์ฉํ์ฌ ๋์์ ์ง์ ์ ์ฅ์์ ๋ํ ์ง๋ฌธ์ ์ํํ์ฌ์ผ ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ๊ด๋ จ๋ Blog์ธ Multi-RAG์ Multi-Region LLM๋ก ํ๊ตญ์ด Chatbot ๋ง๋ค๊ธฐ๋ฅผ ์ฐธ์กฐํฉ๋๋ค.
from multiprocessing import Process, Pipe
processes = []
parent_connections = []
for rag in capabilities:
parent_conn, child_conn = Pipe()
parent_connections.append(parent_conn)
process = Process(target = retrieve_process_from_RAG, args = (child_conn, revised_question, top_k, rag))
processes.append(process)
for process in processes:
process.start()
for parent_conn in parent_connections:
rel_docs = parent_conn.recv()
if (len(rel_docs) >= 1):
for doc in rel_docs:
relevant_docs.append(doc)
for process in processes:
process.join()
def retrieve_process_from_RAG(conn, query, top_k, rag_type):
relevant_docs = []
if rag_type == 'kendra':
rel_docs = retrieve_from_kendra(query=query, top_k=top_k)
else:
rel_docs = retrieve_from_vectorstore(query=query, top_k=top_k, rag_type=rag_type)
if(len(rel_docs)>=1):
for doc in rel_docs:
relevant_docs.append(doc)
conn.send(relevant_docs)
conn.close()
์ฌ๋ฌ ๋ฆฌ์ ์ LLM์ ๋ํ profile์ ์ ์ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ cdk-korean-chatbot-stack.ts์ ์ฐธ์กฐํฉ๋๋ค.
const claude3_sonnet = [
{
"bedrock_region": "us-west-2", // Oregon
"model_type": "claude3",
"model_id": "anthropic.claude-3-sonnet-20240229-v1:0",
"maxOutputTokens": "4096"
},
{
"bedrock_region": "us-east-1", // N.Virginia
"model_type": "claude3",
"model_id": "anthropic.claude-3-sonnet-20240229-v1:0",
"maxOutputTokens": "4096"
}
];
const profile_of_LLMs = claude3_sonnet;
Bedrock์์ client๋ฅผ ์ง์ ํ ๋ bedrock_region์ ์ง์ ํ ์ ์์ต๋๋ค. ์๋์ ๊ฐ์ด LLM์ ์ ํํ๋ฉด Lambda์ event๊ฐ ์ฌ๋๋ง๋ค ๋ค๋ฅธ ๋ฆฌ์ ์ LLM์ ํ์ฉํ ์ ์์ต๋๋ค.
from langchain_aws import ChatBedrock
profile_of_LLMs = json.loads(os.environ.get('profile_of_LLMs'))
selected_LLM = 0
def get_chat(profile_of_LLMs, selected_LLM):
profile = profile_of_LLMs[selected_LLM]
bedrock_region = profile['bedrock_region']
modelId = profile['model_id']
print(f'LLM: {selected_LLM}, bedrock_region: {bedrock_region}, modelId: {modelId}')
maxOutputTokens = int(profile['maxOutputTokens'])
# bedrock
boto3_bedrock = boto3.client(
service_name='bedrock-runtime',
region_name=bedrock_region,
config=Config(
retries = {
'max_attempts': 30
}
)
)
parameters = {
"max_tokens":maxOutputTokens,
"temperature":0.1,
"top_k":250,
"top_p":0.9,
"stop_sequences": [HUMAN_PROMPT]
}
# print('parameters: ', parameters)
chat = ChatBedrock( # new chat model
model_id=modelId,
client=boto3_bedrock,
model_kwargs=parameters,
)
return chat
lambda(chat)์ ๊ฐ์ด ๋ฌธ์๋ฅผ ๋ฒ์ญํ ๋์์ ๋ณ๋ ฌ๋ก ์กฐํํ๊ธฐ ์ํ์ฌ, Lambda์ Multi thread๋ฅผ ์ด์ฉํฉ๋๋ค. ์ด๋, ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ ํ ๋์๋ Pipe()์ ์ด์ฉํฉ๋๋ค.
def translate_relevant_documents_using_parallel_processing(docs):
selected_LLM = 0
relevant_docs = []
processes = []
parent_connections = []
for doc in docs:
parent_conn, child_conn = Pipe()
parent_connections.append(parent_conn)
chat = get_chat(profile_of_LLMs, selected_LLM)
bedrock_region = profile_of_LLMs[selected_LLM]['bedrock_region']
process = Process(target=translate_process_from_relevent_doc, args=(child_conn, chat, doc, bedrock_region))
processes.append(process)
selected_LLM = selected_LLM + 1
if selected_LLM == len(profile_of_LLMs):
selected_LLM = 0
for process in processes:
process.start()
for parent_conn in parent_connections:
doc = parent_conn.recv()
relevant_docs.append(doc)
for process in processes:
process.join()
#print('relevant_docs: ', relevant_docs)
return relevant_docs
Agent ํ์ฉ์์๋ LangChain์ ReAct Agent๋ฅผ ์ ์ํฉ๋๋ค. ์ฌ๊ธฐ์์๋ ๊ต๋ณด๋ฌธ์ Search API๋ฅผ ์ด์ฉํด ๋์์ ๋ณด๋ฅผ ์กฐํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
ํ์ ๊ฒ์์ ์ํด ๋จผ์ ํ๊ตญ์ด๋ก RAG๋ฅผ ์กฐํํ๊ณ , ์์ด๋ก ๋ฒ์ญํ ํ์ ๊ฐ๊ฐ์ ๊ด๋ จ๋ ๋ฌธ์๋ค(Relevant Documents)๋ฅผ ๋ฒ์ญํฉ๋๋ค. ๊ด๋ จ๋ ๋ฌธ์๋ค์ ๋ํด ์ง๋ฌธ์ ๋ฐ๋ผ ๊ด๋ จ์ฑ์ ๋น๊ตํ์ฌ ๊ด๋ จ๋๊ฐ ๋์ ๋ฌธ์์์๋ก Context๋ฅผ ๋ง๋ค์ด์ ํ์ฉํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ๊ด๋ จ๋ Blog์ธ ํ์ ๋์ ๊ฒ์ ๋ฐ ์ธํฐ๋ท ๊ฒ์์ ํ์ฉํ์ฌ RAG๋ฅผ ํธ๋ฆฌํ๊ฒ ํ์ฉํ๊ธฐ์ ์ฐธ์กฐํฉ๋๋ค.
translated_revised_question = traslation_to_english(llm=llm, msg=revised_question)
relevant_docs_using_translated_question = retrieve_from_vectorstore(query=translated_revised_question, top_k=4, rag_type=rag_type)
docs_translation_required = []
if len(relevant_docs_using_translated_question)>=1:
for i, doc in enumerate(relevant_docs_using_translated_question):
if isKorean(doc)==False:
docs_translation_required.append(doc)
else:
relevant_docs.append(doc)
translated_docs = translate_relevant_documents_using_parallel_processing(docs_translation_required)
for i, doc in enumerate(translated_docs):
relevant_docs.append(doc)
Multi-RAG๋ฅผ ์ด์ฉํ์ฌ ์ฌ๋ฌ๊ฐ์ ์ง์ ์ ์ฅ์์ ๊ด๋ จ๋ ๋ฌธ์๋ฅผ ์กฐํํ์์์๋ ๋ฌธ์๊ฐ ์๋ค๋ฉด, ๊ตฌ๊ธ ์ธํฐ๋ท ๊ฒ์์ ํตํด ์ป์ด์ง ๊ฒฐ๊ณผ๋ฅผ ํ์ฉํฉ๋๋ค. ์ฌ๊ธฐ์, assessed_score๋ priority search์ FAISS์ Score๋ก ์ ๋ฐ์ดํธ ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Google Search API ๊ด๋ จ๋ Blog์ธ ํ์ ๋์ ๊ฒ์ ๋ฐ ์ธํฐ๋ท ๊ฒ์์ ํ์ฉํ์ฌ RAG๋ฅผ ํธ๋ฆฌํ๊ฒ ํ์ฉํ๊ธฐ์ ์ฐธ์กฐํฉ๋๋ค.
from googleapiclient.discovery import build
google_api_key = os.environ.get('google_api_key')
google_cse_id = os.environ.get('google_cse_id')
api_key = google_api_key
cse_id = google_cse_id
relevant_docs = []
try:
service = build("customsearch", "v1", developerKey = api_key)
result = service.cse().list(q = revised_question, cx = cse_id).execute()
print('google search result: ', result)
if "items" in result:
for item in result['items']:
api_type = "google api"
excerpt = item['snippet']
uri = item['link']
title = item['title']
confidence = ""
assessed_score = ""
doc_info = {
"rag_type": 'search',
"api_type": api_type,
"confidence": confidence,
"metadata": {
"source": uri,
"title": title,
"excerpt": excerpt,
},
"assessed_score": assessed_score,
}
relevant_docs.append(doc_info)
Multi-RAG, ํ์ ๋์ ๊ฒ์, ์ธํฐ๋ท ๊ฒ์๋ฑ์ ํ์ฉํ์ฌ ๋ค์์ ๊ด๋ จ๋ ๋ฌธ์๊ฐ ๋์ค๋ฉด, ๊ด๋ จ๋๊ฐ ๋์ ์์๋๋ก ์ผ๋ถ ๋ฌธ์๋ง์ RAG์์ ํ์ฉํฉ๋๋ค. ์ด๋ฅผ ์ํด Faiss์ similarity search๋ฅผ ์ด์ฉํฉ๋๋ค. ์ด๊ฒ์ ์ ๋๋ ๊ฐ์ ๊ด๋ จ๋๋ฅผ ์ป์ ์ ์์ด์, ๊ด๋ จ๋์ง ์์ ๋ฌธ์๋ฅผ Context๋ก ํ์ฉํ์ง ์๋๋ก ํด์ค๋๋ค.
selected_relevant_docs = []
if len(relevant_docs)>=1:
selected_relevant_docs = priority_search(revised_question, relevant_docs, bedrock_embeddings)
def priority_search(query, relevant_docs, bedrock_embeddings):
excerpts = []
for i, doc in enumerate(relevant_docs):
if doc['metadata']['translated_excerpt']:
content = doc['metadata']['translated_excerpt']
else:
content = doc['metadata']['excerpt']
excerpts.append(
Document(
page_content=content,
metadata={
'name': doc['metadata']['title'],
'order':i,
}
)
)
embeddings = bedrock_embeddings
vectorstore_confidence = FAISS.from_documents(
excerpts, # documents
embeddings # embeddings
)
rel_documents = vectorstore_confidence.similarity_search_with_score(
query=query,
k=top_k
)
docs = []
for i, document in enumerate(rel_documents):
order = document[0].metadata['order']
name = document[0].metadata['name']
assessed_score = document[1]
relevant_docs[order]['assessed_score'] = int(assessed_score)
if assessed_score < 200:
docs.append(relevant_docs[order])
return docs
Kendra ๋ฅผ ์ด์ฉํ RAG์ ๊ตฌํ์ ๋ฐ๋ผ Kendra์ RAG ์ฑ๋ฅ์ ํฅ์ ์ํฌ ์ ์์ต๋๋ค. Kendra์ FAQ์ ๊ฐ์ด ์ ๋ฆฌ๋ ๋ฌธ์๋ฅผ ํ์ฉํ๊ณ , ๊ด๋ จ๋ ๊ธฐ๋ฐ์ผ๋ก ๊ด๋ จ ๋ฌธ์๋ฅผ ์ ํํ์ฌ Context๋ก ํ์ธ ํฉ๋๋ค. Kendra์์ ๋ฌธ์ ๋ฑ๋ก์ ํ์ํ ๋ด์ฉ์ kendra-document.md์ ์ฐธ์กฐํฉ๋๋ค. ๋ํ, ์์ธํ ๋ด์ฉ์ ๊ด๋ จ๋ Blog์ธ Amazon Bedrock์ Claude์ Amazon Kendra๋ก ํฅ์๋ RAG ์ฌ์ฉํ๊ธฐ์ ์ฐธ๊ณ ํฉ๋๋ค.
RAG์ ์ ์ฅ๋ ๊ธฐ์กด ์ฝ๋๋ฅผ ์ด์ฉํ์ฌ ์๋ก์ด ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. rag-code-generation๋ Code๋ฅผ ํ๊ตญ์ด๋ก ์์ฝํ์ฌ RAG์ ์ ์ฅํ๊ณ ๊ฒ์ํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ์ต๋๋ค. ์ฌ๊ธฐ์์๋ ์ผ๋ฐ ๋ฌธ์์ Code reference๋ฅผ ํ๋์ RAG์ ์ ์ฅํ๊ณ ํ์ฉํฉ๋๋ค.
BedrockEmbeddings์ ์ด์ฉํ์ฌ Embedding์ ํฉ๋๋ค. 'amazon.titan-embed-text-v1'์ Titan Embeddings Generation 1 (G1)์ ์๋ฏธํ๋ฉฐ 8k token์ ์ง์ํฉ๋๋ค.
bedrock_embeddings = BedrockEmbeddings(
client=boto3_bedrock,
region_name = bedrock_region,
model_id = 'amazon.titan-embed-text-v1'
)
์ฌ๊ธฐ์๋ Knowledge Store๋ก OpenSearch, Faiss, Kendra์ ์ด์ฉํฉ๋๋ค.
lambda-chat-ws๋ ์ธ์ ๋ ๋ฉ์์ง์ userId๋ฅผ ์ด์ฉํ์ฌ map_chain์ ์ ์ฅ๋ ๋ํ ์ด๋ ฅ(memory_chain)๊ฐ ์๋์ง ํ์ธํฉ๋๋ค. ์ฑํ ์ด๋ ฅ์ด ์๋ค๋ฉด ์๋์ ๊ฐ์ด ConversationBufferWindowMemory๋ก memory_chain์ ์ค์ ํฉ๋๋ค. ์ฌ๊ธฐ์,
map_chain = dict()
if userId in map_chain:
print('memory exist. reuse it!')
memory_chain = map_chain[userId]
else:
memory_chain = ConversationBufferWindowMemory(memory_key="chat_history", output_key='answer', return_messages=True, k=10)
map_chain[userId] = memory_chain
allowTime = getAllowTime()
load_chat_history(userId, allowTime)
msg = general_conversation(connectionId, requestId, chat, text)
def general_conversation(connectionId, requestId, chat, query):
if isKorean(query)==True :
system = (
"๋ค์์ Human๊ณผ Assistant์ ์น๊ทผํ ์ด์ ๋ํ์
๋๋ค. Assistant์ ์ํฉ์ ๋ง๋ ๊ตฌ์ฒด์ ์ธ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ถฉ๋ถํ ์ ๊ณตํฉ๋๋ค. Assistant์ ์ด๋ฆ์ ์์ฐ์ด๊ณ , ๋ชจ๋ฅด๋ ์ง๋ฌธ์ ๋ฐ์ผ๋ฉด ์์งํ ๋ชจ๋ฅธ๋ค๊ณ ๋งํฉ๋๋ค."
)
else:
system = (
"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."
)
human = "{input}"
prompt = ChatPromptTemplate.from_messages([("system", system), MessagesPlaceholder(variable_name="history"), ("human", human)])
history = memory_chain.load_memory_variables({})["chat_history"]
chain = prompt | chat
try:
isTyping(connectionId, requestId)
stream = chain.invoke(
{
"history": history,
"input": query,
}
)
msg = readStreamMsg(connectionId, requestId, stream.content)
msg = stream.content
print('msg: ', msg)
except Exception:
err_msg = traceback.format_exc()
print('error message: ', err_msg)
sendErrorMessage(connectionId, requestId, err_msg)
raise Exception ("Not able to request to LLM")
return msg
์๋ก์ด Diaglog๋ ์๋์ ๊ฐ์ด chat_memory์ ์ถ๊ฐํฉ๋๋ค.
memory_chain.chat_memory.add_user_message(text)
memory_chain.chat_memory.add_ai_message(msg)
์ฌ๊ธฐ์ stream์ ์๋์ ๊ฐ์ ๋ฐฉ์์ผ๋ก WebSocket์ ์ฌ์ฉํ๋ client์ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ ๊ด๋ จ๋ Blog์ธ Amazon Bedrock์ ์ด์ฉํ์ฌ Stream ๋ฐฉ์์ ํ๊ตญ์ด Chatbot ๊ตฌํํ๊ธฐ์ ์ฐธ๊ณ ํฉ๋๋ค.
def readStreamMsg(connectionId, requestId, stream):
msg = ""
if stream:
for event in stream:
msg = msg + event
result = {
'request_id': requestId,
'msg': msg
}
sendMessage(connectionId, result)
print('msg: ', msg)
return msg
์ฌ๊ธฐ์ client๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ sendMessage()๋ ์๋์ ๊ฐ์ต๋๋ค. ์ฌ๊ธฐ์๋ boto3์ post_to_connection๋ฅผ ์ด์ฉํ์ฌ ๋ฉ์์ง๋ฅผ WebSocket์ endpoint์ธ API Gateway๋ก ์ ์กํฉ๋๋ค.
def sendMessage(id, body):
try:
client.post_to_connection(
ConnectionId=id,
Data=json.dumps(body)
)
except:
raise Exception ("Not able to send a message")
Log์ ๋ํ ํผ๋ฏธ์ ์ด ํ์ํฉ๋๋ค.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudwatch:GenerateQuery",
"logs:*"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
๊ฐ๋ฐ ๋ฐ ํ ์คํธ๋ฅผ ์ํด Kendra์์ ์ถ๊ฐ๋ก S3๋ฅผ ๋ฑ๋กํ ์ ์๋๋ก ๋ชจ๋ S3์ ๋ํ ์ฝ๊ธฐ ํผ๋ฏธ์ ์ ๋ถ์ฌํฉ๋๋ค.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:Describe*",
"s3:Get*",
"s3:List*"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
์ด๋ฅผ CDK๋ก ๊ตฌํํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
const kendraLogPolicy = new iam.PolicyStatement({
resources: ['*'],
actions: ["logs:*", "cloudwatch:GenerateQuery"],
});
roleKendra.attachInlinePolicy( // add kendra policy
new iam.Policy(this, `kendra-log-policy-for-${projectName}`, {
statements: [kendraLogPolicy],
}),
);
const kendraS3ReadPolicy = new iam.PolicyStatement({
resources: ['*'],
actions: ["s3:Get*", "s3:List*", "s3:Describe*"],
});
roleKendra.attachInlinePolicy( // add kendra policy
new iam.Policy(this, `kendra-s3-read-policy-for-${projectName}`, {
statements: [kendraS3ReadPolicy],
}),
);
Quota Console - File size์ ๊ฐ์ด Kendra์ ์ฌ๋ฆด์ ์๋ ํ์ผํฌ๊ธฐ๋ 50MB๋ก ์ ํ๋ฉ๋๋ค. ์ด๋ Quota ์กฐ์ ์์ฒญ์ ์ํด ์ ์ ํ ๊ฐ์ผ๋ก ์กฐ์ ํ ์ ์์ต๋๋ค. ๋ค๋ง ์ด ๊ฒฝ์ฐ์๋ ํ์ผ ํ๊ฐ์์ ์ป์ด๋ผ์ ์๋ Text์ ํฌ๊ธฐ๋ 5MB๋ก ์ ํ๋ฉ๋๋ค. msg๋ฅผ ํ๊ตญ์ด Speech๋ก ๋ณํํ ํ์ CloudFront URL์ ์ด์ฉํ์ฌ S3์ ์ ์ฅ๋ Speech๋ฅผ URI๋ก ๊ณต์ ํ ์ ์์ต๋๋ค.
Amazon Polly๋ฅผ ์ด์ฉํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ํ๊ตญ์ด๋ก ์ฝ์ด์ค๋๋ค. start_speech_synthesis_task์ ํ์ฉํฉ๋๋ค.
def get_text_speech(path, speech_prefix, bucket, msg):
ext = "mp3"
polly = boto3.client('polly')
try:
response = polly.start_speech_synthesis_task(
Engine='neural',
LanguageCode='ko-KR',
OutputFormat=ext,
OutputS3BucketName=bucket,
OutputS3KeyPrefix=speech_prefix,
Text=msg,
TextType='text',
VoiceId='Seoyeon'
)
print('response: ', response)
except Exception:
err_msg = traceback.format_exc()
print('error message: ', err_msg)
raise Exception ("Not able to create voice")
object = '.'+response['SynthesisTask']['TaskId']+'.'+ext
print('object: ', object)
return path+speech_prefix+parse.quote(object)
S3๋ฅผ ๋ฐ์ดํฐ ์์ค๋ฅด ์ถ๊ฐํ ๋ ์๋์ ๊ฐ์ด ์ํํ๋ฉด ๋๋, languageCode๊ฐ ๋ฏธ์ง์๋์ด์ CLI๋ก ๋์ฒดํฉ๋๋ค.
const cfnDataSource = new kendra.CfnDataSource(this, `s3-data-source-${projectName}`, {
description: 'S3 source',
indexId: kendraIndex,
name: 'data-source-for-upload-file',
type: 'S3',
// languageCode: 'ko',
roleArn: roleKendra.roleArn,
// schedule: 'schedule',
dataSourceConfiguration: {
s3Configuration: {
bucketName: s3Bucket.bucketName,
documentsMetadataConfiguration: {
s3Prefix: 'metadata/',
},
inclusionPrefixes: ['documents/'],
},
},
});
CLI ๋ช ๋ น์ด ์์ ์ ๋๋ค.
aws kendra create-data-source
--index-id azfbd936-4929-45c5-83eb-bb9d458e8348
--name data-source-for-upload-file
--type S3
--role-arn arn:aws:iam::123456789012:role/role-lambda-chat-ws-for-korean-chatbot-us-west-2
--configuration '{"S3Configuration":{"BucketName":"storage-for-korean-chatbot-us-west-2", "DocumentsMetadataConfiguration": {"S3Prefix":"metadata/"},"InclusionPrefixes": ["documents/"]}}'
--language-code ko
--region us-west-2
Python client์ ๋ฐ๋ผ OpenSearch๋ฅผ ํ์ฉํฉ๋๋ค.
opensearch-py๋ฅผ ์ค์นํฉ๋๋ค.
pip install opensearch-py
Index naming restrictions์ ๋ฐ๋ index๋ low case์ฌ์ผํ๊ณ , ๊ณต๋ฐฑ์ด๋ ','์ ๊ฐ์ง์ ์์ต๋๋ค.
Vector ๊ฒ์(Sementaic) ๋ฟ ์๋๋ผ, Lexical ๊ฒ์(Keyword)์ ํ์ฉํ์ฌ ๊ด๋ จ๋ ๋ฌธ์๋ฅผ ์ฐพ์ ํ์จ์ ๋์ ๋๋ค. ์์ธํ ๋ด์ฉ์ OpenSearch์์ Lexical ๊ฒ์์ ์์ต๋๋ค.
๋ฌธ์ ์์ฑ์ ์ ๋ฐ์ดํธ๊น์ง ๊ณ ๋ คํ์ฌ index๋ฅผ ์ฒดํฌํ์ฌ ์ง์ฐ๋ ๋ฐฉ์์ ์ฌ์ฉํ์์ผ๋ shard๊ฐ ๊ณผ๋ํ๊ฒ ์ฆ๊ฐํ์ฌ, metadata์ ids๋ฅผ ์ ์ฅํ ์ง์ฐ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ์์ต๋๋ค. lambda-document-manager์ ์ฐธ์กฐํฉ๋๋ค. ๋์์ ํ์ผ ์ ๋ฐ์ดํธ์ meta์์ ์ด์ document๋ค์ ์ฐพ์์ ์ง์ฐ๊ณ ์๋ก์ด ๋ฌธ์๋ฅผ ์ฝ์ ๋๋ค.
def store_document_for_opensearch(docs, key):
objectName = (key[key.find(s3_prefix)+len(s3_prefix)+1:len(key)])
metadata_key = meta_prefix+objectName+'.metadata.json'
delete_document_if_exist(metadata_key)
try:
response = vectorstore.add_documents(docs, bulk_size = 2000)
except Exception:
err_msg = traceback.format_exc()
print('error message: ', err_msg)
#raise Exception ("Not able to request to LLM")
print('uploaded into opensearch')
return response
def delete_document_if_exist(metadata_key):
try:
s3r = boto3.resource("s3")
bucket = s3r.Bucket(s3_bucket)
objs = list(bucket.objects.filter(Prefix=metadata_key))
print('objs: ', objs)
if(len(objs)>0):
doc = s3r.Object(s3_bucket, metadata_key)
meta = doc.get()['Body'].read().decode('utf-8')
print('meta: ', meta)
ids = json.loads(meta)['ids']
print('ids: ', ids)
result = vectorstore.delete(ids)
print('result: ', result)
else:
print('no meta file: ', metadata_key)
except Exception:
err_msg = traceback.format_exc()
print('error message: ', err_msg)
raise Exception ("Not able to create meta file")
์๋๋ OpenSearch์์ Embedding์ ํ ๋ bulk_size ๊ธฐ๋ณธ๊ฐ์ธ 500์ ์ฌ์ฉํ ๋์ ์๋ฌ์ ๋๋ค. ๋ฌธ์๋ฅผ embeddingํ๊ธฐ ์ํด 1840๋ฒ embedding์ ํด์ผํ๋๋ฐ, bulk_size๊ฐ 500์ด๋ฏ๋ก ์๋ฌ๊ฐ ๋ฐ์ํ์์ต๋๋ค.
RuntimeError: The embeddings count, 1840 is more than the [bulk_size], 500. Increase the value of [bulk_size].
bulk_size๋ฅผ 2000์ผ๋ก ๋ณ๊ฒฝํ์ฌ ํด๊ฒฐํฉ๋๋ค.
new_vectorstore = OpenSearchVectorSearch(
index_name=index_name,
is_aoss = False,
#engine="faiss", # default: nmslib
embedding_function = bedrock_embeddings,
opensearch_url = opensearch_url,
http_auth=(opensearch_account, opensearch_passwd),
)
response = new_vectorstore.add_documents(docs, bulk_size = 2000)
CDK ๊ตฌํ ์ฝ๋์์๋ Typescript๋ก ์ธํ๋ผ๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์ธํ ์ค๋ช ํ๊ณ ์์ต๋๋ค.
์ด ์๋ฃจ์ ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ฌ์ ์ ์๋์ ๊ฐ์ ์ค๋น๊ฐ ๋์ด์ผ ํฉ๋๋ค.
์ธํ๋ผ ์ค์น์ ๋ฐ๋ผ CDK๋ก ์ธํ๋ผ ์ค์น๋ฅผ ์งํํฉ๋๋ค.
"Conversation Type"์ผ๋ก [General Conversation]์ ์ ํํ๊ณ , dice.png ํ์ผ์ ๋ค์ด๋ก๋ํฉ๋๋ค.
์ดํ์ ์ฑํ ์ฐฝ ์๋์ ํ์ผ ๋ฒํผ์ ์ ํํ์ฌ ์ ๋ก๋ํฉ๋๋ค. ์ด๋์ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ต๋๋ค.
fsi_faq_ko.csv์ ๋ค์ด๋ก๋ํ ํ์ ํ์ผ ์์ด์ฝ์ ์ ํํ์ฌ ์ ๋ก๋ํํ, ์ฑํ ์ฐฝ์ "๊ฐํธ์กฐํ ์๋น์ค๋ฅผ ์๋ฌธ์ผ๋ก ์ฌ์ฉํ ์ ์๋์?โ ๋ผ๊ณ ์ ๋ ฅํฉ๋๋ค. ์ด๋์ ๊ฒฐ๊ณผ๋ ๏ผ์๋์คโ์ ๋๋ค. ์ด๋์ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ต๋๋ค.
์ฑํ ์ฐฝ์ "์ด์ฒด๋ฅผ ํ ์ ์๋ค๊ณ ๋์ต๋๋ค. ์ด๋ป๊ฒ ํด์ผ ํ๋์?โ ๋ผ๊ณ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
์ฑํ ์ฐฝ์ "๊ฐํธ์กฐํ ์๋น์ค๋ฅผ ์๋ฌธ์ผ๋ก ์ฌ์ฉํ ์ ์๋์?โ ๋ผ๊ณ ์ ๋ ฅํฉ๋๋ค. "์๋ฌธ๋ฑ ํน์์๋ ๊ฐํธ์กฐํ์๋น์ค ์ด์ฉ๋ถ๊ฐ"ํ๋ฏ๋ก ์ข๋ ์์ธํ ์ค๋ช ์ ์ป์์ต๋๋ค.
์ฑํ ์ฐฝ์ "๊ณต๋์ธ์ฆ์ ์ฐฝ๊ตฌ๋ฐ๊ธ ์๋น์ค๋ ๋ฌด์์ธ๊ฐ์?"๋ผ๊ณ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
์ฑํ ์ฐฝ์์ ๋ค๋ก๊ฐ๊ธฐ ํ ํ์ "1-2 Agent"๋ฅผ ์ ํํฉ๋๋ค. ์๋์ ๊ฐ์ด "์ฌํ ๊ด๋ จ ๋์ ์ถ์ฒํด์ค."์ ๊ฐ์ด ์ ๋ ฅํ๋ฉด ๊ต๋ณด๋ฌธ๊ณ ์ API๋ฅผ ์ด์ฉํ์ฌ "์ฌํ"๊ณผ ๊ด๋ จ๋ ๋ฌธ์๋ฅผ ์กฐํํ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
"์์ธ์ ์ค๋ ๋ ์จ ์๋ ค์ค"๋ผ๊ณ ์ ๋ ฅํ๋ฉด ์๋์ ๊ฐ์ด ๋ ์จ ์ ๋ณด๋ฅผ ์กฐํํ์ฌ ๋ณด์ฌ์ค๋๋ค.
LLM์ ์๊ฐ์ ๋ฌผ์ด๋ณด๋ฉด ๋ง์ง๋ง Training ์๊ฐ์ด๋ ์ ํ ๊ด๋ จ์๋ Hallucination ๊ฐ์ ์ค๋๋ค. Agent๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ์ ์๋์ ๊ฐ์ด ํ์ฌ ์๊ฐ์ ์กฐํํ์ฌ ๋ณด์ฌ์ค๋๋ค. "์ค๋ ๋ ์ง ์๋ ค์ค."์ "ํ์ฌ ์๊ฐ์?"์ ์ด์ฉํ์ฌ ๋์์ ํ์ธํฉ๋๋ค.
"์์จ์ Lex ์๋น์ค๋ ๋ฌด์์ธ์ง ์ค๋ช ํด์ค."์ ๊ฐ์ด ์๋ชป๋ ๋จ์ด๋ฅผ ์กฐํฉํ์ฌ ์ง๋ฌธํ์์ต๋๋ค.
"Amazon Varco ์๋น์ค๋ฅผ Manufactoring์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ ์๋ ค์ค."๋ก ์ง๋ฌธํ๊ณ ์๋ต์ ํ์ธํฉ๋๋ค.
"Amazon์ Athena ์๋น์ค์ ๋ํด ์ค๋ช ํด์ฃผ์ธ์."๋ก ๊ฒ์ํ ๋ ํ์ ๋์ ๊ฒ์์ ํ๋ฉด ์์ด ๋ฌธ์์์ ๋ต๋ณ์ ํ์ํ ๊ด๋ จ๋ฌธ์๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค.
ํ์๋์ ๊ฒ์์ ํ์ง ์์์๋์ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ต๋๋ค. ๋์ผํ ์ง๋ฌธ์ด์ง๋ง, OpenSearch์ ๊ฒฐ๊ณผ๋ฅผ ๋ง์ด ์ฐธ์กฐํ์ฌ ์๋ชป๋ ๋ต๋ณ์ ํ ์ ์์ต๋๋ค.
"์๋ง์กด ๋ฒ ๋๋ฝ์ ์ด์ฉํ์ฌ ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ํธ์ํ ๋ํ๋ฅผ ์ฆ๊ธฐ์ค์ ์์ผ๋ฉฐ, ํ์ผ์ ์ ๋ก๋ํ๋ฉด ์์ฝ์ ํ ์ ์์ต๋๋ค.โ๋ก ์ ๋ ฅํ๊ณ ๋ฒ์ญ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
โ์์ฌ ๊ฐ์ฑ๋น ์ข์ต๋๋ค. ์์น๊ฐ ์ข๊ณ ์ค์นด์ด๋ผ์ด์ง ๋ฐ๋ฒ ํ / ์ผ๊ฒฝ ์ต๊ณฑ๋๋ค. ์์ฌ์ ๋ ์ ยท ์งํ์ฃผ์ฐจ์ฅ์ด ๋น์ข์ต๋๋ค.. ํธํ ์ ๊ตํต์ด ๋๋ฌด ๋ณต์กํด์ ์ฃผ๋ณ์์ค์ ์ด์ฉํ๊ธฐ ์ด๋ ต์ต๋๋ค. / ํ๊ฐ๋๊ฐ๋ ๊ธธ / ์ฃผ๋ณ์์ค์ ๋๊ฐ๋ ๋ฐฉ๋ฒ๋ฑ.. ํ์ํฉ๋๋ค.โ๋ฅผ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
โJohn Park. Solutions Architectย |ย WWCS Amazon Web Services Email:ย john@amazon.com Mobile:ย +82-10-1234-5555โ๋ก ์ ๋ ฅํ์ ์ด๋ฉ์ผ์ด ์ถ์ถ๋๋์ง ํ์ธํฉ๋๋ค.
PII(Personal Identification Information)์ ์ญ์ ์ ์๋ ์๋์ ๊ฐ์ต๋๋ค. "John Park, Ph.D. Solutions Architect | WWCS Amazon Web Services Email: john@amazon.com Mobile: +82-10-1234-4567"์ ๊ฐ์ด ์ ๋ ฅํ์ฌ name, phone number, address๋ฅผ ์ญ์ ํ ํ ์คํธ๋ฅผ ์ป์ต๋๋ค. ํ๋กฌํํธ๋ PII๋ฅผ ์ฐธ์กฐํฉ๋๋ค.
"To have a smoth conversation with a chatbot, it is better for usabilities to show responsesess in a stream-like, conversational maner rather than waiting until the complete answer."๋ก ์ค๋ฅ๊ฐ ์๋ ๋ฌธ์ฅ์ ์ ๋ ฅํฉ๋๋ค.
"Chatbot๊ณผ ์ํ ํ ๋ฐํ๋ฅผ ์ํด์๋ ์ฌ์ฉ์์ ์ง๋ฌธ์ฅ ๋ํ ๋ต๋ณ์ ์์ ํ ์ป์ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋ณด๋ค๋ Stream ํํ๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์ข์ต๋๋ค."๋ก ์ ๋ ฅํ์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
"I have two pet cats. One of them is missing a leg. The other one has a normal number of legs for a cat to have. In total, how many legs do my cats have?"๋ฅผ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
"๋ด ๊ณ ์์ด ๋ ๋ง๋ฆฌ๊ฐ ์๋ค. ๊ทธ์ค ํ ๋ง๋ฆฌ๋ ๋ค๋ฆฌ๊ฐ ํ๋ ์๋ค. ๋ค๋ฅธ ํ ๋ง๋ฆฌ๋ ๊ณ ์์ด๊ฐ ์ ์์ ์ผ๋ก ๊ฐ์ ธ์ผ ํ ๋ค๋ฆฌ ์๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ์ ์ฒด์ ์ผ๋ก ๋ณด์์ ๋, ๋ด ๊ณ ์์ด๋ค์ ๋ค๋ฆฌ๊ฐ ๋ช ๊ฐ๋ ์์๊น?"๋ก ์ง๋ฌธ์ ์ ๋ ฅํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
๋ฉ๋ด์์ "Timestamp Extraction"์ ์ ํํ๊ณ , "์ง๊ธ์ 2023๋ 12์ 5์ผ 18์ 26๋ถ์ด์ผ"๋ผ๊ณ ์ ๋ ฅํ๋ฉด prompt๋ฅผ ์ด์ฉํด ์๋์ฒ๋ผ ์๊ฐ์ ์ถ์ถํฉ๋๋ค.
์ค์ ๊ฒฐ๊ณผ ๋ฉ์์ง๋ ์๋์ ๊ฐ์ต๋๋ค.
<result>
<year>2023</year>
<month>12</month>
<day>05</day>
<hour>18</hour>
<minute>26</minute>
</result>
๋ํ์ ์๋์ ๋ง์ถ์ด์ ์ง๋ฌธ์ ๋ต๋ณ์ํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ฅผํ ๋ฉด [General Conversation]์์ "์ฐํ๊ฐ ํฌ๋ฆฌ์ค๋ง์ค์ ์ ๋ฌผ์ ๊ฐ์ ธ๋ค ์ค๊น?"๋ก ์ง๋ฌธ์ ํ๋ฉด ์๋์ ๊ฐ์ด ๋ต๋ณํฉ๋๋ค.
[9. Child Conversation (few shot)]์ผ๋ก ์ ํํฉ๋๋ค. ๋์ผํ ์ง๋ฌธ์ ํฉ๋๋ค. ์๋์ ๋ง์ถ์ด์ ์ ์ ํ ๋ต๋ณ์ ํ ์ ์์์ต๋๋ค.
๋์ด์ ์ธํ๋ผ๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ์ ์๋์ฒ๋ผ ๋ชจ๋ ๋ฆฌ์์ค๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค.
-
API Gateway Console๋ก ์ ์ํ์ฌ "rest-api-for-stream-chatbot", "ws-api-for-stream-chatbot"์ ์ญ์ ํฉ๋๋ค.
-
Cloud9 console์ ์ ์ํ์ฌ ์๋์ ๋ช ๋ น์ด๋ก ์ ์ฒด ์ญ์ ๋ฅผ ํฉ๋๋ค.
cdk destroy --all
LLM์ ์ฌ์ฉํ Enterprise์ฉ application์ ๊ฐ๋ฐํ๊ธฐ ์ํด์๋ ๊ธฐ์ ์ด ๊ฐ์ง ๋ค์ํ ์ ๋ณด๋ฅผ ํ์ฉํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด Fine-tuning์ด๋ RAG๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค. Fine-tuning์ ์ผ๋ฐ์ ์ผ๋ก RAG๋ณด๋ค ์ฐ์ํ ์ฑ๋ฅ์ ๊ธฐ๋ํ ์ ์์ผ๋, ๋ค์ํ application์์ ํ์ฉํ๊ธฐ ์ํด์ ๋ง์ ๋น์ฉ๊ณผ ์ํ์ฐฉ์ค๊ฐ ์์ ์ ์์ต๋๋ค. RAG๋ ๋ฐ์ดํฐ์ ๋น ๋ฅธ ์ ๋ฐ์ดํธ ๋ฐ ๋น์ฉ๋ฉด์์ ํ์ฉ๋๊ฐ ๋์์, Fine-tuning๊ณผ RAG๋ฅผ ๋ณํํ์ฌ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋ณผ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์์๋ RAG์ ์ฑ๋ฅ์ ํฅ์์ํค๋ฆฌ ์ํด ๋ค์ํ ๊ธฐ์ ์ ํตํฉํ๊ณ , ์ด๋ฅผ ํ์ฉํ ์ ์๋ Korean Chatbot์ ๋ง๋ค์์ต๋๋ค. ์ด๋ฅผ ํตํด ๋ค์ํ RAG ๊ธฐ์ ๋ค์ ํ ์คํธํ๊ณ ์ฌ์ฉํ๋ ์ฉ๋์ ๋ง๊ฒ RAG ๊ธฐ์ ์ ํ์ฉํ ์ ์์ต๋๋ค.