From 7d62637a15319d1bc57d08f0d3056f34c85ed6fb Mon Sep 17 00:00:00 2001 From: Alex Kira Date: Wed, 29 Nov 2023 13:11:17 -0800 Subject: [PATCH] docs[patch]: Add intro to LCEL doc --- docs/docs/expression_language/interface.ipynb | 2 +- docs/docs/expression_language/intro.ipynb | 267 ++++++++++++++++++ 2 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 docs/docs/expression_language/intro.ipynb diff --git a/docs/docs/expression_language/interface.ipynb b/docs/docs/expression_language/interface.ipynb index 2e1253fbe62..8161a92a14a 100644 --- a/docs/docs/expression_language/interface.ipynb +++ b/docs/docs/expression_language/interface.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "source": [ "---\n", - "sidebar_position: 0\n", + "sidebar_position: 1\n", "title: Interface\n", "---" ] diff --git a/docs/docs/expression_language/intro.ipynb b/docs/docs/expression_language/intro.ipynb new file mode 100644 index 00000000000..6dab59f3237 --- /dev/null +++ b/docs/docs/expression_language/intro.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "366a0e68-fd67-4fe5-a292-5c33733339ea", + "metadata": {}, + "source": [ + "---\n", + "sidebar_position: 0\n", + "title: Intro to LCEL\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "9a9acd2e", + "metadata": {}, + "source": [ + "## Basic Prompt Example\n", + "\n", + "LCEL allows us to compose different components together to make a chain. This allows us to build complex chains from basic components, while supporting out of the box functionality such as streaming, parallelism, and logging. \n", + "\n", + "Let’s start out with a basic use case, we want to use a prompt template and invoke a chain to run through Open AI’s LLM to generate a short Haiku:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "466b65b3", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chat_models import ChatOpenAI\n", + "from langchain.prompts import ChatPromptTemplate\n", + "from langchain.schema.output_parser import StrOutputParser\n", + "\n", + "prompt = ChatPromptTemplate.from_template(\"tell me a short joke about {topic}\")\n", + "model = ChatOpenAI()\n", + "output_parser = StrOutputParser()\n", + "chain = prompt | model | output_parser\n", + "chain.invoke({\"topic\": \"ice cream\"})" + ] + }, + { + "cell_type": "markdown", + "id": "81c502c5-85ee-4f36-aaf4-d6e350b7792f", + "metadata": {}, + "source": [ + "Notice this line of this code, where we piece together then different components into a single chain using LCEL:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e628359e-1dee-4e39-b327-2d894be3fe6d", + "metadata": {}, + "outputs": [], + "source": [ + "chain = prompt | model | output_parser" + ] + }, + { + "cell_type": "markdown", + "id": "ed43ca97-8ba0-458d-800a-9cd631aba82e", + "metadata": {}, + "source": [ + "In this case, we will invoke this chain with some user input which is first fed into the prompt, and then fed through model, and finally to string output parser. \n", + "\n", + "The `|` symbol is similar to a unix pipe operator, which chains together the different components feeds the output from one component as input into the next component. \n", + "\n", + "To follow this along:\n", + "\n", + "1. We pass in user input on the desired topic as `{\"topic\": \"ice cream\"}`\n", + "2. The `prompt` component takes the user input, which is then used to construct a PromptValue after using the `topic` to construct the prompt. \n", + "3. The `model` component takes the generated prompt, and passes into the OpenAI LLM model for evaluation. The generated output from the model is a `ChatMessage` object. \n", + "4. Finally, the `output_parser` component takes in a `ChatMessage`, and transforms this into a Python string, which is returned from the invoke method. \n", + "\n", + "`Todo: Maybe Diagram`\n", + "\n", + "Note that if you’re curious about the output of any components, you can always test out a smaller version of the chain such as `prompt` or `prompt | model` to see the intermediate results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11089b6f-23f8-474f-97ec-8cae8d0ca6d4", + "metadata": {}, + "outputs": [], + "source": [ + "input = {\"topic\": \"ice cream\"}\n", + "\n", + "prompt.invoke(input)\n", + "# > ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])\n", + "\n", + "(prompt | model).invoke(input)\n", + "# > AIMessage(content=\"Why did the ice cream go to therapy?\\nBecause it had too many toppings and couldn't cone-trol itself!\")" + ] + }, + { + "cell_type": "markdown", + "id": "cc7d3b9d-e400-4c9b-9188-f29dac73e6bb", + "metadata": {}, + "source": [ + "## RAG Search Example\n", + "\n", + "For our next example, we want to run a retrieval-augmented generation chain to add some context when responding to questions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "662426e8-4316-41dc-8312-9b58edc7e0c9", + "metadata": {}, + "outputs": [], + "source": [ + "# Requires:\n", + "# pip install \"langchain[docarray]\"\n", + "\n", + "from langchain.embeddings import OpenAIEmbeddings\n", + "from langchain.prompts import ChatPromptTemplate\n", + "from langchain.schema.runnable import RunnableParallel, RunnablePassthrough\n", + "from langchain.vectorstores import DocArrayInMemorySearch\n", + "\n", + "vectorstore = DocArrayInMemorySearch.from_texts(\n", + " [\"harrison worked at kensho\", \"bears like to eat honey\"],\n", + " embedding=OpenAIEmbeddings(),\n", + ")\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + "setup_and_retrieval = RunnableParallel(\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + ")\n", + "chain = setup_and_retrieval | prompt | model | output_parser\n", + "\n", + "chain.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "f0999140-6001-423b-970b-adf1dfdb4dec", + "metadata": {}, + "source": [ + "In this case, the composed chain is: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b88e9bb-f04a-4a56-87ec-19a0e6350763", + "metadata": {}, + "outputs": [], + "source": [ + "chain = setup_and_retrieval | prompt | model | output_parser" + ] + }, + { + "cell_type": "markdown", + "id": "6e929e15-40a5-4569-8969-384f636cab87", + "metadata": {}, + "source": [ + "To explain this, we first can see that the prompt template above takes in `context` and `question` as values to be substituted in the prompt. Before building the prompt template, we want to retrieve relevant documents to the search and include them as part of the context. \n", + "\n", + "As a preliminary step, we’ve setup the retriever using an in memory store, which can retrieve documents based on a query. This is a runnable component as well that can be chained together with other components, but you can also try to run it separately:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7319ef6-613b-4638-ad7d-4a2183702c1d", + "metadata": {}, + "outputs": [], + "source": [ + "retriever.invoke(\"where did harrison work?\")" + ] + }, + { + "cell_type": "markdown", + "id": "e6833844-f1c4-444c-a3d2-31b3c6b31d46", + "metadata": {}, + "source": [ + "We then use the `RunnableParallel` to prepare the expected inputs into the prompt by using the entries for the retrieved documents as well as the original user question, using the retriever for document search, and RunnablePassthrough to pass the user’s question:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcbca26b-d6b9-4c24-806c-1ec8fdaab4ed", + "metadata": {}, + "outputs": [], + "source": [ + "setup_and_retrieval = RunnableParallel(\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "68c721c1-048b-4a64-9d78-df54fe465992", + "metadata": {}, + "source": [ + "To review, the complete chain is:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d5115a7-7b8e-458b-b936-26cc87ee81c4", + "metadata": {}, + "outputs": [], + "source": [ + "setup_and_retrieval = RunnableParallel(\n", + " {\"context\": retriever, \"question\": RunnablePassthrough()}\n", + ")\n", + "chain = setup_and_retrieval | prompt | model | output_parser" + ] + }, + { + "cell_type": "markdown", + "id": "5c6f5f74-b387-48a0-bedd-1fae202cd10a", + "metadata": {}, + "source": [ + "With the flow being:\n", + "\n", + "1. The first steps create a `RunnableMap` object with two entries. The first entry, `context` will include the document results fetched by the retriever. The second entry, `question` will contain the user’s original question. To pass on the question, we use `RunnablePassthrough` to copy this entry. \n", + "2. Feed the dictionary from the step above to the `prompt` component. It then takes the user input which is `question` as well as the retrieved document which is `context` to construct a prompt and output a PromptValue. \n", + "3. The `model` component takes the generated prompt, and passes into the OpenAI LLM model for evaluation. The generated output from the model is a `ChatMessage` object. \n", + "4. Finally, the `output_parser` component takes in a `ChatMessage`, and transforms this into a Python string, which is returned from the invoke method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5218cafd-370a-4e3a-85a0-452e570092fe", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}