# Concept
- 선별된 문서들 각각의 답변(Response)을 구한 뒤, 모든 답변을 토대로 최종 답변을 구하는 `Model`이다.
- 각 문서의 답변을 구하는 `map_docs_chain`과 최종 답변을 구하는 `final_Chain`으로 구성되어 있다.
- `map_chain`의 경우 `map_docs_chain`에서 얻은 결과를 `final_Chain`에 넘겨주는 역할을 수행한다.
- [Map Reduce Process 이미지](https://js.langchain.com/v0.1/docs/modules/chains/document/map_reduce/) ![[Map Reduce Chain Process.jpg]]
# Code
- `from langchain.schema.runnable import RunnableLambda`을 이용해 `map_chain`을 사용할 때마다 `map_docs` function을 수행하도록 할 수 있다.
- `RunnableLambda`는 `input parameter`를 무조건 하나를 받는데, type이 **`dictionary` or `callable object`** 이어야 한다.
- 이를 이용해 `retriver`에서 받은 모든 문서들을 `for`문을 통해 문서 하나하나마다 `map_docs_chain`을 거쳐 `question`에 관한 답을 얻는다.
- 얻은 답들은 `join`을 통해 하나의 문장으로 만들어진다. 하나의 문장으로 만드는 이유는 `final_Chain`에서 각각의 답변을 하나의 `context` String value로 받기 떄문이다.
- `final_Chain`과 `map_chain`, `map_docs_chain` 모두 `question` value을 받는데, 이는 `final_Chain` `invoke`에서 받은 값을 `RunnablePassthrough()`로 넘겨주면 된다.
- `map_docs_prompt`에서 `verbatim`라는 단어를 사용함으로써 **중간에 model이 값을 수정하지 않도록** 설정한다.
- [[Stuff LCEL Chain]]과 마찬가지로 `final_prompt`에 `If you don't know the answer, just say that you don't know. Don't try to make up an answer` 문장을 사용하여 model이 잘못된 정보를 생성하지 않도록 설정한다.
```python
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import UnstructuredFileLoader
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import LocalFileStore
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
llm = ChatOpenAI(temperature=0.1)
loader = UnstructuredFileLoader("./files/chapter_one.txt")
splitter = CharacterTextSplitter.from_tiktoken_encoder(
separator="\n\n",
chunk_size=500,
chunk_overlap=60,
)
doc = loader.load_and_split(text_splitter=splitter)
cache_dir = LocalFileStore("./.cache/")
embedder = OpenAIEmbeddings()
cache_embedder = CacheBackedEmbeddings.from_bytes_store(embedder, cache_dir)
vectorStore = Chroma.from_documents(doc, cache_embedder)
retriver = vectorStore.as_retriever()
map_docs_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Use the following portion of a long document to see if any of the
text is relevant to answer the question. Return any relevant text
verbatim.
------
{context}
""",
),
("human", "{question}"),
]
)
map_docs_chain = map_docs_prompt | llm
def map_docs(inputs):
documents = inputs["documents"]
question = inputs["question"]
return "\n\n".join(
map_docs_chain.invoke(
{
"context": doc.page_content,
"question": question,
}
).content
for doc in documents
)
map_chain = {"documents": retriver, "question": RunnablePassthrough()} | RunnableLambda(
map_docs
)
final_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Given the following extracted parts of a long document and a
question, create a final answer.
If you don't know the answer, just say that you don't know. Don't try
to make up an answer
------
{context}
""",
),
("human", "{question}"),
]
)
final_Chain = (
{"context": map_chain, "question": RunnablePassthrough()} | final_prompt | llm
)
final_Chain.invoke("Describe Victory Mansions")
```