이 노트북은 langchain-weaviate 패키지를 사용하여 LangChain에서 Weaviate vector store를 시작하는 방법을 다룹니다.
Weaviate는 오픈소스 vector database입니다. 좋아하는 ML 모델의 데이터 객체와 vector embedding을 저장하고, 수십억 개의 데이터 객체로 원활하게 확장할 수 있습니다.
이 통합을 사용하려면 실행 중인 Weaviate database 인스턴스가 필요합니다.

최소 버전

이 모듈은 Weaviate 1.23.7 이상이 필요합니다. 그러나 최신 버전의 Weaviate를 사용하는 것을 권장합니다.

Weaviate에 연결하기

이 노트북에서는 http://localhost:8080에서 실행 중인 로컬 인스턴스가 있고 gRPC traffic을 위해 포트 50051이 열려 있다고 가정합니다. 따라서 다음과 같이 Weaviate에 연결합니다:
weaviate_client = weaviate.connect_to_local()

기타 배포 옵션

Weaviate는 Weaviate Cloud Services (WCS), Docker 또는 Kubernetes를 사용하는 등 다양한 방식으로 배포할 수 있습니다. Weaviate 인스턴스가 다른 방식으로 배포된 경우, Weaviate에 연결하는 다양한 방법에 대해 여기에서 자세히 읽어보세요. 다양한 helper 함수를 사용하거나 custom 인스턴스를 생성할 수 있습니다.
weaviate.WeaviateClient 객체를 생성하는 v4 client API가 필요합니다.

인증

WCS에서 실행되는 인스턴스와 같은 일부 Weaviate 인스턴스는 API key 및/또는 username+password 인증과 같은 인증이 활성화되어 있습니다. 자세한 내용은 client 인증 가이드심층 인증 구성 페이지를 참조하세요.

기존 collection에 연결하기 (index 재사용)

로컬 Weaviate 인스턴스에 이미 collection을 생성한 경우 직접 연결할 수 있습니다:
from langchain_weaviate import WeaviateVectorStore

store = WeaviateVectorStore(
    client=weaviate_client,
    index_name="Test",
    text_key="text",
)

설치

# install package
# pip install -Uqq langchain-weaviate
# pip install openai tiktoken langchain

환경 설정

이 노트북은 OpenAIEmbeddings를 통해 OpenAI API를 사용합니다. OpenAI API key를 얻어 OPENAI_API_KEY라는 이름의 환경 변수로 export하는 것을 권장합니다. 이 작업이 완료되면 OpenAI API key가 자동으로 읽힙니다. 환경 변수가 처음이라면 여기 또는 이 가이드에서 자세히 읽어보세요.

사용법

유사도로 객체 찾기

다음은 데이터 import부터 Weaviate 인스턴스 쿼리까지 query와의 유사도로 객체를 찾는 방법의 예시입니다.

1단계: 데이터 import

먼저, 긴 텍스트 파일의 내용을 로드하고 청크로 나누어 Weaviate에 추가할 데이터를 생성합니다.
from langchain_community.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter
loader = TextLoader("state_of_the_union.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

embeddings = OpenAIEmbeddings()
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.embeddings.openai.OpenAIEmbeddings` was deprecated in langchain-community 0.1.0 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAIEmbeddings`.
  warn_deprecated(
이제 데이터를 import할 수 있습니다. 이를 위해 Weaviate 인스턴스에 연결하고 결과 weaviate_client 객체를 사용합니다. 예를 들어, 아래와 같이 문서를 import할 수 있습니다:
import weaviate
from langchain_weaviate.vectorstores import WeaviateVectorStore
weaviate_client = weaviate.connect_to_local()
db = WeaviateVectorStore.from_documents(docs, embeddings, client=weaviate_client)
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)

2단계: 검색 수행

이제 유사도 검색을 수행할 수 있습니다. 이는 Weaviate에 저장된 embedding과 query 텍스트에서 생성된 동등한 embedding을 기반으로 query 텍스트와 가장 유사한 문서를 반환합니다.
query = "What did the president say about Ketanji Brown Jackson"
docs = db.similarity_search(query)

# Print the first 100 characters of each result
for i, doc in enumerate(docs):
    print(f"\nDocument {i + 1}:")
    print(doc.page_content[:100] + "...")
Document 1:
Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Ac...

Document 2:
And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of ...

Document 3:
Vice President Harris and I ran for office with a new economic vision for America.

Invest in Ameri...

Document 4:
A former top litigator in private practice. A former federal public defender. And from a family of p...
filter 조건에 따라 결과를 포함하거나 제외하는 filter를 추가할 수도 있습니다. (더 많은 filter 예제를 참조하세요.)
from weaviate.classes.query import Filter

for filter_str in ["blah.txt", "state_of_the_union.txt"]:
    search_filter = Filter.by_property("source").equal(filter_str)
    filtered_search_results = db.similarity_search(query, filters=search_filter)
    print(len(filtered_search_results))
    if filter_str == "state_of_the_union.txt":
        assert len(filtered_search_results) > 0  # There should be at least one result
    else:
        assert len(filtered_search_results) == 0  # There should be no results
0
4
반환할 결과 수의 상한선인 k를 제공하는 것도 가능합니다.
search_filter = Filter.by_property("source").equal("state_of_the_union.txt")
filtered_search_results = db.similarity_search(query, filters=search_filter, k=3)
assert len(filtered_search_results) <= 3

결과 유사도 정량화

선택적으로 관련성 “score”를 검색할 수 있습니다. 이는 검색 결과 풀 중에서 특정 검색 결과가 얼마나 좋은지를 나타내는 상대적 점수입니다. 이것은 상대적 점수이므로 관련성 임계값을 결정하는 데 사용해서는 안 됩니다. 그러나 전체 검색 결과 세트 내에서 서로 다른 검색 결과의 관련성을 비교하는 데 사용할 수 있습니다.
docs = db.similarity_search_with_score("country", k=5)

for doc in docs:
    print(f"{doc[1]:.3f}", ":", doc[0].page_content[:100] + "...")
0.935 : For that purpose we’ve mobilized American ground forces, air squadrons, and ship deployments to prot...
0.500 : And built the strongest, freest, and most prosperous nation the world has ever known.

Now is the h...
0.462 : If you travel 20 miles east of Columbus, Ohio, you’ll find 1,000 empty acres of land.

It won’t loo...
0.450 : And my report is this: the State of the Union is strong—because you, the American people, are strong...
0.442 : Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Ac...

검색 메커니즘

similarity_search는 Weaviate의 hybrid search를 사용합니다. hybrid search는 vector 검색과 keyword 검색을 결합하며, alpha는 vector 검색의 가중치입니다. similarity_search 함수를 사용하면 kwargs로 추가 인수를 전달할 수 있습니다. 사용 가능한 인수는 이 참조 문서를 참조하세요. 따라서 아래와 같이 alpha=0을 추가하여 순수 keyword 검색을 수행할 수 있습니다:
docs = db.similarity_search(query, alpha=0)
docs[0]
Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'source': 'state_of_the_union.txt'})

지속성

langchain-weaviate를 통해 추가된 모든 데이터는 구성에 따라 Weaviate에 지속됩니다. 예를 들어 WCS 인스턴스는 데이터를 무기한 지속하도록 구성되어 있으며, Docker 인스턴스는 volume에 데이터를 지속하도록 설정할 수 있습니다. Weaviate의 지속성에 대해 자세히 읽어보세요.

Multi-tenancy

Multi-tenancy를 사용하면 단일 Weaviate 인스턴스에서 동일한 collection 구성으로 많은 수의 격리된 데이터 collection을 가질 수 있습니다. 이는 각 최종 사용자가 자신의 격리된 데이터 collection을 갖는 SaaS 앱 구축과 같은 다중 사용자 환경에 적합합니다. multi-tenancy를 사용하려면 vector store가 tenant 매개변수를 인식해야 합니다. 따라서 데이터를 추가할 때 아래와 같이 tenant 매개변수를 제공하세요.
db_with_mt = WeaviateVectorStore.from_documents(
    docs, embeddings, client=weaviate_client, tenant="Foo"
)
2024-Mar-26 03:40 PM - langchain_weaviate.vectorstores - INFO - Tenant Foo does not exist in index LangChain_30b9273d43b3492db4fb2aba2e0d6871. Creating tenant.
그리고 쿼리를 수행할 때도 tenant 매개변수를 제공하세요.
db_with_mt.similarity_search(query, tenant="Foo")
[Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'source': 'state_of_the_union.txt'}),
 Document(page_content='And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of food, gas, housing, and so much more. \n\nI understand. \n\nI remember when my Dad had to leave our home in Scranton, Pennsylvania to find work. I grew up in a family where if the price of food went up, you felt it. \n\nThat’s why one of the first things I did as President was fight to pass the American Rescue Plan.  \n\nBecause people were hurting. We needed to act, and we did. \n\nFew pieces of legislation have done more in a critical moment in our history to lift us out of crisis. \n\nIt fueled our efforts to vaccinate the nation and combat COVID-19. It delivered immediate economic relief for tens of millions of Americans.  \n\nHelped put food on their table, keep a roof over their heads, and cut the cost of health insurance. \n\nAnd as my Dad used to say, it gave people a little breathing room.', metadata={'source': 'state_of_the_union.txt'}),
 Document(page_content='He and his Dad both have Type 1 diabetes, which means they need insulin every day. Insulin costs about $10 a vial to make.  \n\nBut drug companies charge families like Joshua and his Dad up to 30 times more. I spoke with Joshua’s mom. \n\nImagine what it’s like to look at your child who needs insulin and have no idea how you’re going to pay for it.  \n\nWhat it does to your dignity, your ability to look your child in the eye, to be the parent you expect to be. \n\nJoshua is here with us tonight. Yesterday was his birthday. Happy birthday, buddy.  \n\nFor Joshua, and for the 200,000 other young people with Type 1 diabetes, let’s cap the cost of insulin at $35 a month so everyone can afford it.  \n\nDrug companies will still do very well. And while we’re at it let Medicare negotiate lower prices for prescription drugs, like the VA already does.', metadata={'source': 'state_of_the_union.txt'}),
 Document(page_content='Putin’s latest attack on Ukraine was premeditated and unprovoked. \n\nHe rejected repeated efforts at diplomacy. \n\nHe thought the West and NATO wouldn’t respond. And he thought he could divide us at home. Putin was wrong. We were ready.  Here is what we did.   \n\nWe prepared extensively and carefully. \n\nWe spent months building a coalition of other freedom-loving nations from Europe and the Americas to Asia and Africa to confront Putin. \n\nI spent countless hours unifying our European allies. We shared with the world in advance what we knew Putin was planning and precisely how he would try to falsely justify his aggression.  \n\nWe countered Russia’s lies with truth.   \n\nAnd now that he has acted the free world is holding him accountable. \n\nAlong with twenty-seven members of the European Union including France, Germany, Italy, as well as countries like the United Kingdom, Canada, Japan, Korea, Australia, New Zealand, and many others, even Switzerland.', metadata={'source': 'state_of_the_union.txt'})]

Retriever 옵션

Weaviate는 retriever로도 사용할 수 있습니다

Maximal marginal relevance search (MMR)

retriever 객체에서 similaritysearch를 사용하는 것 외에도 mmr을 사용할 수 있습니다.
retriever = db.as_retriever(search_type="mmr")
retriever.invoke(query)[0]
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'source': 'state_of_the_union.txt'})

LangChain과 함께 사용하기

대규모 언어 모델(LLM)의 알려진 한계는 학습 데이터가 오래되었거나 필요한 특정 도메인 지식을 포함하지 않을 수 있다는 것입니다. 아래 예시를 살펴보세요:
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm.predict("What did the president say about Justice Breyer")
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.
  warn_deprecated(
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `predict` was deprecated in LangChain 0.1.7 and will be removed in 0.2.0. Use invoke instead.
  warn_deprecated(
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
"I'm sorry, I cannot provide real-time information as my responses are generated based on a mixture of licensed data, data created by human trainers, and publicly available data. The last update was in October 2021."
Vector store는 관련 정보를 저장하고 검색하는 방법을 제공하여 LLM을 보완합니다. 이를 통해 LLM의 추론 및 언어 능력과 vector store의 관련 정보 검색 능력을 결합하여 LLM과 vector store의 강점을 결합할 수 있습니다. LLM과 vector store를 결합하는 두 가지 잘 알려진 애플리케이션은 다음과 같습니다:
  • Question answering
  • Retrieval-augmented generation (RAG)

출처가 있는 Question Answering

langchain의 question answering은 vector store를 사용하여 향상될 수 있습니다. 이것이 어떻게 수행되는지 살펴보겠습니다. 이 섹션은 Index에서 문서를 조회하는 RetrievalQAWithSourcesChain을 사용합니다. 먼저, 텍스트를 다시 청크로 나누고 Weaviate vector store로 import합니다.
from langchain.chains import RetrievalQAWithSourcesChain
from langchain_openai import OpenAI
with open("state_of_the_union.txt") as f:
    state_of_the_union = f.read()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)
docsearch = WeaviateVectorStore.from_texts(
    texts,
    embeddings,
    client=weaviate_client,
    metadatas=[{"source": f"{i}-pl"} for i in range(len(texts))],
)
이제 retriever를 지정하여 chain을 구성할 수 있습니다:
chain = RetrievalQAWithSourcesChain.from_chain_type(
    OpenAI(temperature=0), chain_type="stuff", retriever=docsearch.as_retriever()
)
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.llms.openai.OpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAI`.
  warn_deprecated(
그리고 chain을 실행하여 질문을 합니다:
chain(
    {"question": "What did the president say about Justice Breyer"},
    return_only_outputs=True,
)
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `__call__` was deprecated in LangChain 0.1.0 and will be removed in 0.2.0. Use invoke instead.
  warn_deprecated(
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
{'answer': ' The president thanked Justice Stephen Breyer for his service and announced his nomination of Judge Ketanji Brown Jackson to the Supreme Court.\n',
 'sources': '31-pl'}

Retrieval-Augmented Generation

LLM과 vector store를 결합하는 또 다른 매우 인기 있는 애플리케이션은 retrieval-augmented generation (RAG)입니다. 이는 retriever를 사용하여 vector store에서 관련 정보를 찾은 다음 LLM을 사용하여 검색된 데이터와 prompt를 기반으로 출력을 제공하는 기술입니다. 유사한 설정으로 시작합니다:
with open("state_of_the_union.txt") as f:
    state_of_the_union = f.read()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)
docsearch = WeaviateVectorStore.from_texts(
    texts,
    embeddings,
    client=weaviate_client,
    metadatas=[{"source": f"{i}-pl"} for i in range(len(texts))],
)

retriever = docsearch.as_retriever()
검색된 정보가 template에 채워지도록 RAG 모델용 template을 구성해야 합니다.
from langchain_core.prompts import ChatPromptTemplate

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. Use three sentences maximum and keep the answer concise.
Question: {question}
Context: {context}
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)

print(prompt)
input_variables=['context', 'question'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], 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. Use three sentences maximum and keep the answer concise.\nQuestion: {question}\nContext: {context}\nAnswer:\n"))]
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
그리고 셀을 실행하면 매우 유사한 출력을 얻습니다.
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

rag_chain.invoke("What did the president say about Justice Breyer")
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
  warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)
"The president honored Justice Stephen Breyer for his service to the country as an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. The president also mentioned nominating Circuit Court of Appeals Judge Ketanji Brown Jackson to continue Justice Breyer's legacy of excellence. The president expressed gratitude towards Justice Breyer and highlighted the importance of nominating someone to serve on the United States Supreme Court."
하지만 template은 직접 구성하는 것이므로 필요에 맞게 사용자 정의할 수 있습니다.

마무리 및 리소스

Weaviate는 확장 가능하고 프로덕션 준비가 완료된 vector store입니다. 이 통합을 통해 Weaviate를 LangChain과 함께 사용하여 강력한 데이터 저장소로 대규모 언어 모델의 기능을 향상시킬 수 있습니다. 확장성과 프로덕션 준비 상태는 LangChain 애플리케이션의 vector store로 훌륭한 선택이 되며, 프로덕션까지의 시간을 단축할 수 있습니다.
Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I