Skip to main content

Use RunnableMaps

RunnableMaps make it easy to execute multiple Runnables in parallel, and to return the output of these Runnables as a map.

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnableMap


model = ChatOpenAI()
joke_chain = ChatPromptTemplate.from_template("tell me a joke about {topic}") | model
poem_chain = ChatPromptTemplate.from_template("write a 2-line poem about {topic}") | model

map_chain = RunnableMap({"joke": chain1, "poem": chain2,})

map_chain.invoke({"topic": "bear"})
    {'joke': AIMessage(content="Why don't bears wear shoes? \nBecause they have bear feet!", additional_kwargs={}, example=False),
'poem': AIMessage(content="In twilight's embrace, a bear's gentle lumber,\nSilent strength, nature's awe, a humble slumber.", additional_kwargs={}, example=False)}

Manipulating outputs/inputs

Maps can be useful for manipulating the output of one Runnable to match the input format of the next Runnable in a sequence.

from langchain.embeddings import OpenAIEmbeddings
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from langchain.vectorstores import FAISS

vectorstore = FAISS.from_texts(["harrison worked at kensho"], embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

retrieval_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)

retrieval_chain.invoke("where did harrison work?")
    'Harrison worked at Kensho.'

Here the input to prompt is expected to be a map with keys "context" and "question". The user input is just the question. So we need to get the context using our retriever and passthrough the user input under the "question" key.

Note that when composing a RunnableMap when another Runnable we don't even need to wrap our dictuionary in the RunnableMap class — the type conversion is handled for us.

Parallelism

RunnableMaps are also useful for running independent processes in parallel, since each Runnable in the map is executed in parallel. For example, we can see our earlier joke_chain, poem_chain and map_chain all have about the same runtime, even though map_chain executes both of the other two.

joke_chain.invoke({"topic": "bear"})
    958 ms ± 402 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
poem_chain.invoke({"topic": "bear"})
    1.22 s ± 508 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
map_chain.invoke({"topic": "bear"})
    1.15 s ± 119 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)