LangChainとOpenAIによる質問応答

このノートブックについて

LangChainとOpenAIを使用したRAG質問応答システムの構築方法を学びます。このノートブックでは、ドキュメントをチャンク化し、ベクトル埋め込みを生成し、Elasticsearchをベクトルストアとして使用してコンテキストを取得し、正確で対話型のAI応答を実現する方法を説明します。

Colabで開くノートブックをダウンロード
Notebook

This interactive notebook uses Langchain to split fictional workplace documents into passages and uses OpenAI to transform these passages into embeddings and store them into Elasticsearch.

Then when we ask a question, we retrieve the relevant passages from the vector store and use langchain and OpenAI to provide a summary for the question.

Install required packages

!python3 -m pip install -qU langchain langchain-elasticsearch openai==0.28.1 tiktoken jq
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. langserve 0.0.21 requires pydantic<2,>=1, but you have pydantic 2.3.0 which is incompatible.  [notice] A new release of pip is available: 23.2 -> 23.3.1 [notice] To update, run: pip install --upgrade pip

Connect to Elasticsearch

ℹ️ We're using an Elastic Cloud deployment of Elasticsearch for this notebook. If you don't have an Elastic Cloud deployment, sign up here for a free trial.

We'll use the Cloud ID to identify our deployment, because we are using Elastic Cloud deployment. To find the Cloud ID for your deployment, go to https://cloud.elastic.co/deployments and select your deployment.

We will use ElasticsearchStore to connect to our elastic cloud deployment. This would help create and index data easily. In the ElasticsearchStore instance, will set embedding to OpenAIEmbeddings to embed the texts and elasticsearch index name that will be used in this example.

from langchain_elasticsearch import ElasticsearchStore
from langchain.embeddings.openai import OpenAIEmbeddings
from getpass import getpass

# https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id
ELASTIC_CLOUD_ID = getpass("Elastic Cloud ID: ")

# https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#creating-an-api-key
ELASTIC_API_KEY = getpass("Elastic Api Key: ")

# https://platform.openai.com/api-keys
OPENAI_API_KEY = getpass("OpenAI API key: ")

embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)

vector_store = ElasticsearchStore(
    es_cloud_id=ELASTIC_CLOUD_ID,
    es_api_key=ELASTIC_API_KEY,
    index_name="workplace_index",
    embedding=embeddings,
)

Indexing Data into Elasticsearch

Let's download the sample dataset and deserialize the document.

from urllib.request import urlopen
from langchain.llms import OpenAI
import json

url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/example-apps/chatbot-rag-app/data/data.json"

response = urlopen(url)
data = json.load(response)

with open("temp.json", "w") as json_file:
    json.dump(data, json_file)

Split Documents into Passages

We’ll chunk documents into passages in order to improve the retrieval specificity and to ensure that we can provide multiple passages within the context window of the final question answering prompt.

Here we are chunking documents into 800 token passages with an overlap of 400 tokens.

Here we are using a simple splitter but Langchain offers more advanced splitters to reduce the chance of context being lost.

from langchain.document_loaders import JSONLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter


def metadata_func(record: dict, metadata: dict) -> dict:
    metadata["name"] = record.get("name")
    metadata["summary"] = record.get("summary")
    metadata["url"] = record.get("url")
    metadata["category"] = record.get("category")
    metadata["updated_at"] = record.get("updated_at")

    return metadata


# For more loaders https://python.langchain.com/docs/modules/data_connection/document_loaders/
# And 3rd party loaders https://python.langchain.com/docs/modules/data_connection/document_loaders/#third-party-loaders
loader = JSONLoader(
    file_path="temp.json",
    jq_schema=".[]",
    content_key="content",
    metadata_func=metadata_func,
)

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=512, chunk_overlap=256
)
docs = loader.load_and_split(text_splitter=text_splitter)

Bulk Import Passages

Now that we have split each document into the chunk size of 800, we will now index data to elasticsearch using ElasticsearchStore.from_documents.

We will use Cloud ID, Password and Index name values set in the Create cloud deployment step.

documents = vector_store.from_documents(
    docs,
    embeddings,
    index_name="workplace_index",
    es_cloud_id=ELASTIC_CLOUD_ID,
    es_api_key=ELASTIC_API_KEY,
)

Asking a question

Now that we have the passages stored in Elasticsearch, we can now ask a question to get the relevant passages.

from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

retriever = vector_store.as_retriever()

llm = OpenAI(openai_api_key=OPENAI_API_KEY)

ANSWER_PROMPT = ChatPromptTemplate.from_template(
    """You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Be as verbose and educational in your response as possible.

    context: {context}
    Question: "{question}"
    Answer:
    """
)

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | ANSWER_PROMPT
    | llm
    | StrOutputParser()
)

ans = chain.invoke("what is the nasa sales team?")

print("---- Answer ----")
print(ans)
---- Answer ---- The NASA Sales Team is responsible for understanding the unique market dynamics and cultural nuances of North and South America. It is led by Area Vice-Presidents Laura Martinez (North America) and Gary Johnson (South America), and consists of dedicated account managers, sales representatives, and support staff. The team works to effectively target and engage with customers across the region.

Add Source Tracing

RAG can provide clear traceability of the source knowledge used to answer a question. This is important for compliance and regulatory reasons and limiting LLM hallucinations. This is known as source tracking.

In this example, we extend the Prompt template to ask the LLM to cite the source of the answer.

from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import ChatPromptTemplate, PromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.schema import format_document

retriever = vector_store.as_retriever()

llm = OpenAI(openai_api_key=OPENAI_API_KEY)

ANSWER_PROMPT = ChatPromptTemplate.from_template(
    """
You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Be as verbose and educational in your response as possible.
Each passage has a SOURCE which is the title of the document. When answering, cite source name of the passages you are answering from below the answer, on a new line, with a prefix of "SOURCE:".


context: {context}
Question: "{question}"
Answer:
"""
)

DOCUMENT_PROMPT = PromptTemplate.from_template(
    """
---
SOURCE: {name}
{page_content}
---
"""
)


def _combine_documents(
    docs, document_prompt=DOCUMENT_PROMPT, document_separator="\n\n"
):
    doc_strings = [format_document(doc, document_prompt) for doc in docs]
    return document_separator.join(doc_strings)


_context = {
    "context": retriever | _combine_documents,
    "question": RunnablePassthrough(),
}

chain = _context | ANSWER_PROMPT | llm | StrOutputParser()

ans = chain.invoke("what is the nasa sales team?")

print("---- Answer ----")
print(ans)
---- Answer ---- The North America South America (NASA) sales team is responsible for serving customers and achieving business objectives across North and South America. The team is led by two Area Vice-Presidents: Laura Martinez is the Area Vice-President of North America, and Gary Johnson is the Area Vice-President of South America. The team consists of dedicated account managers, sales representatives, and support staff. They are responsible for identifying and pursuing new business opportunities, nurturing existing client relationships, and ensuring customer satisfaction. SOURCE: Sales Organization Overview

Returning Passages with Answer

In this example, we extend the chain to return the passages back with the answer. This is helpful for the UI to display the source passages, should the user want to read more on the topic.

from langchain.schema.runnable import RunnableMap
from langchain.prompts import ChatPromptTemplate, PromptTemplate
from langchain.schema import format_document
from operator import itemgetter

retriever = vector_store.as_retriever()

llm = OpenAI(openai_api_key=OPENAI_API_KEY)

ANSWER_PROMPT = ChatPromptTemplate.from_template(
    """
You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Be as verbose and educational in your response as possible.
Each passage has a SOURCE which is the title of the document. When answering, cite source name of the passages you are answering from below the answer, on a new line, with a prefix of "SOURCE:".

context: {context}
Question: {question}
Answer:

"""
)

DOCUMENT_PROMPT = PromptTemplate.from_template(
    """
---
SOURCE: {name}
{page_content}
---
"""
)


def _combine_documents(
    docs, document_prompt=DOCUMENT_PROMPT, document_separator="\n\n"
):
    doc_strings = [format_document(doc, document_prompt) for doc in docs]
    return document_separator.join(doc_strings)


retrieved_documents = RunnableMap(
    docs=itemgetter("question") | retriever,
    question=itemgetter("question"),
)

_context = {
    "context": lambda x: _combine_documents(x["docs"]),
    "question": lambda x: x["question"],
}

answer = {
    "answer": _context | ANSWER_PROMPT | llm,
    "docs": itemgetter("docs"),
}

chain = retrieved_documents | answer

ans = chain.invoke({"question": "what is the nasa sales team?"})

print("---- Answer ----")
print(ans["answer"])
print()
print("---- Documents ----")
for doc in ans["docs"]:
    print(doc.metadata["name"])
    print(doc.page_content)
    print("----")
---- Answer ---- The North America South America (NASA) region has two Area Vice-Presidents: Laura Martinez is the Area Vice-President of North America, and Gary Johnson is the Area Vice-President of South America. The NASA sales team consists of dedicated account managers, sales representatives, and support staff, led by their respective Area Vice-Presidents. They are responsible for identifying and pursuing new business opportunities, nurturing existing client relationships, and ensuring customer satisfaction. The teams collaborate closely with other departments, such as marketing, product development, and customer support, to ensure we consistently deliver high-quality products and services to our clients. SOURCE: Sales Organization Overview ---- Documents ---- Sales Organization Overview Our sales organization is structured to effectively serve our customers and achieve our business objectives across multiple regions. The organization is divided into the following main regions: The Americas: This region includes the United States, Canada, Mexico, as well as Central and South America. The North America South America region (NASA) has two Area Vice-Presidents: Laura Martinez is the Area Vice-President of North America, and Gary Johnson is the Area Vice-President of South America. Europe: Our European sales team covers the entire continent, including the United Kingdom, Germany, France, Spain, Italy, and other countries. The team is responsible for understanding the unique market dynamics and cultural nuances, enabling them to effectively target and engage with customers across the region. The Area Vice-President for Europe is Rajesh Patel. Asia-Pacific: This region encompasses countries such as China, Japan, South Korea, India, Australia, and New Zealand. Our sales team in the Asia-Pacific region works diligently to capitalize on growth opportunities and address the diverse needs of customers in this vast and rapidly evolving market. The Area Vice-President for Asia-Pacific is Mei Li. Middle East & Africa: This region comprises countries across the Middle East and Africa, such as the United Arab Emirates, Saudi Arabia, South Africa, and Nigeria. Our sales team in this region is responsible for navigating the unique market challenges and identifying opportunities to expand our presence and better serve our customers. The Area Vice-President for Middle East & Africa is Jamal Abdi. Each regional sales team consists of dedicated account managers, sales representatives, and support staff, led by their respective Area Vice-Presidents. They are responsible for identifying and pursuing new business opportunities, nurturing existing client relationships, and ensuring customer satisfaction. The teams collaborate closely with other departments, such as marketing, product development, and customer support, to ensure we consistently deliver high-quality products and services to our clients. ---- Sales Engineering Collaboration As an engineer, it is important to understand the sales team's goals and objectives, as this will help you to provide them with the necessary information, tools, and support to successfully sell your company's products and services. Communication: Effective communication is key to successfully working with the sales team. Make sure to maintain open lines of communication, and be responsive to their questions and concerns. This includes: a. Attending sales meetings and conference calls when required. b. Providing regular product updates and training sessions to the sales team. c. Being available to answer technical questions and clarifications. Collaboration: Collaborate with the sales team in developing and refining sales materials, such as product presentations, demos, and technical documents. This will ensure that the sales team has accurate and up-to-date information to present to clients. Additionally, work closely with the sales team on customer projects or product customizations, providing technical guidance, and ensuring that the solutions meet the customer's requirements. Customer Engagement: At times, engineers may be asked to join sales meetings or calls with potential clients to provide technical expertise. In these situations, it is important to: a. Be prepared and understand the customer's needs and pain points. b. Clearly explain the technical aspects of the product or solution in a simple language that the customer can understand. c. Address any concerns or questions the customer may have. Continuous Improvement: Actively seek feedback from the sales team regarding product performance, customer experiences, and market trends. Use this feedback to identify areas of improvement and collaborate with other engineers to enhance the product or service offerings. Mutual Respect and Support: It is essential to treat your colleagues in the sales team with respect and professionalism. Recognize and appreciate their efforts in promoting and selling the company's products and services. In turn, the sales team should also respect and appreciate the technical expertise and knowledge of the engineering team. By working together, both the engineering and sales teams can contribute to the overall success of the company. Conclusion: Collaboration between engineers and the sales team is crucial for a tech company's success. By understanding each other's roles, maintaining effective communication, collaborating on projects, and supporting one another, both teams can work together to achieve the company's goals and ensure customer satisfaction. ---- Fy2024 Company Sales Strategy III. Action Plans A. Sales Team Development: Expand the sales team to cover new markets and industries. Provide ongoing training to sales staff on product knowledge, sales techniques, and industry trends. Implement a performance-based incentive system to reward top performers. B. Marketing and Promotion: Develop targeted marketing campaigns for different customer segments and industries. Leverage digital marketing channels to increase brand visibility and lead generation. Participate in industry events and trade shows to showcase our products and services. C. Partner Ecosystem: Strengthen existing partnerships and establish new strategic alliances to expand market reach. Collaborate with partners on joint marketing and sales initiatives. Provide partner training and support to ensure they effectively represent our products and services. D. Customer Success: Implement a proactive customer success program to improve customer retention and satisfaction. Develop a dedicated customer support team to address customer inquiries and concerns promptly. Collect and analyze customer feedback to identify areas for improvement in our products, services, and processes. IV. Monitoring and Evaluation Establish key performance indicators (KPIs) to track progress toward our objectives. Conduct regular sales team meetings to review performance, share best practices, and address challenges. Conduct quarterly reviews of our sales strategy to ensure alignment with market trends and adjust as needed. By following this sales strategy for fiscal year 2024, our tech company aims to achieve significant growth and success in our target markets, while also providing exceptional value and service to our customers. ---- Sales Engineering Collaboration Title: Working with the Sales Team as an Engineer in a Tech Company Introduction: As an engineer in a tech company, collaboration with the sales team is essential to ensure the success of the company's products and services. This guidance document aims to provide an overview of how engineers can effectively work with the sales team, fostering a positive and productive working environment. Understanding the Sales Team's Role: The sales team is responsible for promoting and selling the company's products and services to potential clients. Their role involves establishing relationships with customers, understanding their needs, and ensuring that the offered solutions align with their requirements. As an engineer, it is important to understand the sales team's goals and objectives, as this will help you to provide them with the necessary information, tools, and support to successfully sell your company's products and services. Communication: Effective communication is key to successfully working with the sales team. Make sure to maintain open lines of communication, and be responsive to their questions and concerns. This includes: a. Attending sales meetings and conference calls when required. b. Providing regular product updates and training sessions to the sales team. c. Being available to answer technical questions and clarifications. Collaboration: Collaborate with the sales team in developing and refining sales materials, such as product presentations, demos, and technical documents. This will ensure that the sales team has accurate and up-to-date information to present to clients. Additionally, work closely with the sales team on customer projects or product customizations, providing technical guidance, and ensuring that the solutions meet the customer's requirements. Customer Engagement: At times, engineers may be asked to join sales meetings or calls with potential clients to provide technical expertise. In these situations, it is important to: ----

Conversational Question Answering

We have achieved getting answers to questions, but what if we want to ask follow up questions? We can use the answer from the previous question as the context for the next question. This is known as conversational question answering.

In this example, we extend the chain to use the answer from the previous question as the context for the next question.

from langchain.schema.runnable import RunnableMap
from langchain.prompts import ChatPromptTemplate, PromptTemplate
from langchain.schema import format_document
from operator import itemgetter

retriever = vector_store.as_retriever()

llm = OpenAI(openai_api_key=OPENAI_API_KEY)

ANSWER_PROMPT = ChatPromptTemplate.from_template(
    """
You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Be as verbose and educational in your response as possible.
Each passage has a SOURCE which is the title of the document. When answering, cite source name of the passages you are answering from below the answer, on a new line, with a prefix of "SOURCE:".

context:
{context}

Question: {question}
Answer:
"""
)

DOCUMENT_PROMPT = PromptTemplate.from_template(
    """
---
SOURCE: {name}
{page_content}
---
"""
)

CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(
    """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.

Chat History:
{chat_history}
Follow Up Input: {question}
"""
)

standalone_question = RunnableMap(
    standalone_question=RunnablePassthrough.assign(
        chat_history=lambda x: _format_chat_history(x["chat_history"])
    )
    | CONDENSE_QUESTION_PROMPT
    | llm
    | StrOutputParser(),
)


def _format_chat_history(chat_history) -> str:
    buffer = ""
    for dialogue_turn in chat_history:
        human = "Human: " + dialogue_turn[0]
        ai = "Assistant: " + dialogue_turn[1]
        buffer += "\n" + "\n".join([human, ai])
    return buffer


def _combine_documents(
    docs, document_prompt=DOCUMENT_PROMPT, document_separator="\n\n"
):
    doc_strings = [format_document(doc, document_prompt) for doc in docs]
    return document_separator.join(doc_strings)


retrieved_documents = RunnableMap(
    docs=itemgetter("standalone_question") | retriever,
    question=itemgetter("standalone_question"),
)

_context = {
    "context": lambda x: _combine_documents(x["docs"]),
    "question": lambda x: x["question"],
}

answer = {
    "answer": _context | ANSWER_PROMPT | llm,
    "docs": itemgetter("docs"),
}

chain = standalone_question | retrieved_documents | answer

ans = chain.invoke(
    {
        "question": "What are their objectives?",
        "chat_history": [
            "What is the nasa sales team?",
            "The sales team of NASA consists of Laura Martinez, the Area "
            "Vice-President of North America, and Gary Johnson, the Area "
            "Vice-President of South America."
            "SOURCE: Sales Organization Overview",
        ],
    }
)

print("---- Answer ----")
print(ans["answer"])
---- Answer ---- The objectives for fiscal year 2024 are to increase revenue by 20% compared to fiscal year 2023, expand market share in key segments by 15%, retain 95% of existing customers and increase customer satisfaction ratings, and launch at least two new products or services in high-demand market segments. SOURCE: Fy2024 Company Sales Strategy

Next Steps

We have shown how to use Langchain to build a question answering system. We have shown how to index data into Elasticsearch, ask a question and use the answer from the previous question as the context for the next question.

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう