Skip to content

Commit 4e6ec18

Browse files
committed
Added initial version of the LangChain RAG example
1 parent f571b65 commit 4e6ec18

File tree

2 files changed

+235
-1
lines changed

2 files changed

+235
-1
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# LangChain"
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {},
14+
"outputs": [],
15+
"source": [
16+
"import os\n",
17+
"import json\n",
18+
"from typing import List\n",
19+
"from dotenv import load_dotenv\n",
20+
"from pymongo import MongoClient\n",
21+
"from langchain.chat_models import AzureChatOpenAI\n",
22+
"from langchain.embeddings import AzureOpenAIEmbeddings\n",
23+
"from langchain.vectorstores import AzureCosmosDBVectorSearch\n",
24+
"from langchain.schema.document import Document\n",
25+
"from langchain.prompts import PromptTemplate\n",
26+
"from langchain.schema import StrOutputParser\n",
27+
"from langchain.schema.runnable import RunnablePassthrough"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"# Load settings for the notebook\n",
37+
"load_dotenv()\n",
38+
"CONNECTION_STRING = os.environ.get(\"DB_CONNECTION_STRING\")\n",
39+
"EMBEDDINGS_DEPLOYMENT_NAME = \"embeddings\"\n",
40+
"COMPLETIONS_DEPLOYMENT_NAME = \"completions\"\n",
41+
"AOAI_ENDPOINT = os.environ.get(\"AOAI_ENDPOINT\")\n",
42+
"AOAI_KEY = os.environ.get(\"AOAI_KEY\")\n",
43+
"AOAI_API_VERSION = \"2023-05-15\""
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": null,
49+
"metadata": {},
50+
"outputs": [],
51+
"source": [
52+
"# Establish Azure OpenAI connectivity\n",
53+
"llm = AzureChatOpenAI( \n",
54+
" temperature = 0,\n",
55+
" openai_api_version = AOAI_API_VERSION,\n",
56+
" azure_endpoint = AOAI_ENDPOINT,\n",
57+
" openai_api_key = AOAI_KEY, \n",
58+
" azure_deployment = \"completions\"\n",
59+
")\n",
60+
"embedding_model = AzureOpenAIEmbeddings(\n",
61+
" openai_api_version = AOAI_API_VERSION,\n",
62+
" azure_endpoint = AOAI_ENDPOINT,\n",
63+
" openai_api_key = AOAI_KEY, \n",
64+
" azure_deployment = \"embeddings\",\n",
65+
" chunk_size=10\n",
66+
")"
67+
]
68+
},
69+
{
70+
"cell_type": "markdown",
71+
"metadata": {},
72+
"source": [
73+
"## Vector search with LangChain\n",
74+
"\n",
75+
"In the previous lab, the `pymongo` library was used to perform a vector search through a db command to find product documents that were most similar to the user's input. In this lab, you will use the `langchain` library to perform the same search. LangChain has a vector store class named **AzureCosmosDBVectorSearch**, a community contribution, that supports vector search in Azure CosmosDB for MongoDB API vCore.\n",
76+
"\n",
77+
"When establishing the connection to the vector store (MongoDB vCore), remember that in previous labs the products collection was populated and a contentVector field added that contains the vectorized embeddings of the document itself. Finally, a vector index was also created on the contentVector field to enable vector search.\n",
78+
"\n",
79+
"The return value of a vector search in LangChain is a list of `Document` objects. The LangChain `Document` class contains two properties: `page_content`, that represents the textual content that is typically used to augment the prompt, and `metadata` that contains all other attributes of the document. In the cell below, we'll use the `_id` field as the page_content, and the rest of the fields are returned as metadata.\n",
80+
"\n",
81+
"The next two cells initiate a connection to the vector store and performs a vector search. Notice how much more concise the code is compared to the previous lab with the addition of LangChain."
82+
]
83+
},
84+
{
85+
"cell_type": "code",
86+
"execution_count": null,
87+
"metadata": {},
88+
"outputs": [],
89+
"source": [
90+
"# Reference the existing vector store\n",
91+
"vector_store = AzureCosmosDBVectorSearch.from_connection_string(\n",
92+
" connection_string = CONNECTION_STRING,\n",
93+
" namespace = \"cosmic_works.products\",\n",
94+
" embedding = embedding_model,\n",
95+
" index_name = \"VectorSearchIndex\", \n",
96+
" embedding_key = \"contentVector\",\n",
97+
" text_key = \"_id\"\n",
98+
")"
99+
]
100+
},
101+
{
102+
"cell_type": "code",
103+
"execution_count": null,
104+
"metadata": {},
105+
"outputs": [],
106+
"source": [
107+
"query = \"What yellow products are there?\"\n",
108+
"vector_store.similarity_search(query, k=3)"
109+
]
110+
},
111+
{
112+
"cell_type": "markdown",
113+
"metadata": {},
114+
"source": [
115+
"## RAG with LangChain\n",
116+
"\n",
117+
"In this section, we'll implement the RAG pattern using LangChain. In LangChain, a **retriever** is used to augment the prompt with contextual data. In this case, the already established vector store will be used as the retriever. By default, the prompt is augmented with the `page_content` field of the retrieved document that customarily contains the text content of the embedded vector. In our case, the document itself serves as the textual content, so we'll have to do some pre-processing to format the text of the product list that is expected in our system prompt (JSON string) - see the **format_documents** function below for this implementation.\n",
118+
"\n",
119+
"We'll also define a reusable RAG [chain](https://python.langchain.com/docs/modules/chains/) to control the flow and behavior of the call into the LLM. This chain is defined using the LCEL syntax (LangChain Expression Language)."
120+
]
121+
},
122+
{
123+
"cell_type": "code",
124+
"execution_count": null,
125+
"metadata": {},
126+
"outputs": [],
127+
"source": [
128+
"# A system prompt describes the responsibilities, instructions, and persona of the AI.\n",
129+
"# Note the addition of the templated variable/placeholder for the list of products and the incoming question.\n",
130+
"system_prompt = \"\"\"\n",
131+
"You are a helpful, fun and friendly sales assistant for Cosmic Works, a bicycle and bicycle accessories store. \n",
132+
"Your name is Cosmo.\n",
133+
"You are designed to answer questions about the products that Cosmic Works sells.\n",
134+
"\n",
135+
"Only answer questions related to the information provided in the list of products below that are represented\n",
136+
"in JSON format.\n",
137+
"\n",
138+
"If you are asked a question that is not in the list, respond with \"I don't know.\"\n",
139+
"\n",
140+
"List of products:\n",
141+
"{products}\n",
142+
"\n",
143+
"Question:\n",
144+
"{question}\n",
145+
"\"\"\""
146+
]
147+
},
148+
{
149+
"cell_type": "code",
150+
"execution_count": null,
151+
"metadata": {},
152+
"outputs": [],
153+
"source": [
154+
"# remember that each Document contains a page_content property\n",
155+
"# that is populated with the _id field of the document\n",
156+
"# all other document fields are located in the metadata property\n",
157+
"def format_docs(docs:List[Document]) -> str:\n",
158+
" \"\"\"\n",
159+
" Prepares the product list for the system prompt.\n",
160+
" \"\"\"\n",
161+
" str_docs = []\n",
162+
" for doc in docs:\n",
163+
" # Build the product document without the contentVector\n",
164+
" doc_dict = {\"_id\": doc.page_content}\n",
165+
" doc_dict.update(doc.metadata)\n",
166+
" if \"contentVector\" in doc_dict: \n",
167+
" del doc_dict[\"contentVector\"]\n",
168+
" str_docs.append(json.dumps(doc_dict)) \n",
169+
" # Return a single string containing each product JSON representation\n",
170+
" # separated by two newlines\n",
171+
" return \"\\n\\n\".join(str_docs)"
172+
]
173+
},
174+
{
175+
"cell_type": "code",
176+
"execution_count": null,
177+
"metadata": {},
178+
"outputs": [],
179+
"source": [
180+
"# Create a retriever from the vector store\n",
181+
"retriever = vector_store.as_retriever()\n",
182+
"\n",
183+
"# Create the prompt template from the system_prompt text\n",
184+
"llm_prompt = PromptTemplate.from_template(system_prompt)\n",
185+
"\n",
186+
"rag_chain = (\n",
187+
" # populate the tokens/placeholders in the llm_prompt \n",
188+
" # products takes the results of the vector store and formats the documents\n",
189+
" # question is a passthrough that takes the incoming question\n",
190+
" { \"products\": retriever | format_docs, \"question\": RunnablePassthrough()}\n",
191+
" | llm_prompt\n",
192+
" # pass the populated prompt to the language model\n",
193+
" | llm\n",
194+
" # return the string ouptut from the language model\n",
195+
" | StrOutputParser()\n",
196+
")"
197+
]
198+
},
199+
{
200+
"cell_type": "code",
201+
"execution_count": null,
202+
"metadata": {},
203+
"outputs": [],
204+
"source": [
205+
"question = \"What are the names and skus of yellow products? Output the answer as a bulleted list.\"\n",
206+
"response = rag_chain.invoke(question)\n",
207+
"print(response)"
208+
]
209+
}
210+
],
211+
"metadata": {
212+
"kernelspec": {
213+
"display_name": ".venv",
214+
"language": "python",
215+
"name": "python3"
216+
},
217+
"language_info": {
218+
"codemirror_mode": {
219+
"name": "ipython",
220+
"version": 3
221+
},
222+
"file_extension": ".py",
223+
"mimetype": "text/x-python",
224+
"name": "python",
225+
"nbconvert_exporter": "python",
226+
"pygments_lexer": "ipython3",
227+
"version": "3.11.5"
228+
}
229+
},
230+
"nbformat": 4,
231+
"nbformat_minor": 2
232+
}

Labs/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ python-dotenv==1.0.0
33
requests==2.31.0
44
pydantic==2.5.2
55
openai==1.6.0
6-
tenacity==8.2.3
6+
tenacity==8.2.3
7+
langchain==0.0.352
8+
tiktoken==0.5.2

0 commit comments

Comments
 (0)