에디터 노트: 이 게시물은 Xata 팀과의 협업으로 작성되었습니다. 저희는 그들의 새로운 통합 기능에 대해 기대하며, 그들과 함께 Q&A 챗봇 구현에 대한 심층적인 질의응답 시간을 가지게 되어 매우 즐거웠습니다.
지난 몇 주 동안, 저희는 네 개의 Xata 통합 기능을 LangChain 저장소에 병합했으며, 오늘 Xata의 런칭 위크(launch week)의 일환으로 이를 공개하게 되어 기쁩니다! 이 블로그 게시물에서는 Xata가 무엇인지, 그리고 왜 AI 애플리케이션을 위한 좋은 데이터 동반자가 될 수 있는지 간략하게 살펴보겠습니다. 또한 Xata 데이터베이스(벡터 스토어 역할)의 정보를 기반으로 질문에 답하고, 장기 메모리를 Xata(메모리 스토어 역할)에 저장하는 Q&A 챗봇 구현 코드 예시도 보여드리겠습니다.
Xata란 무엇인가?
Xata는 PostgreSQL을 기반으로 하는 데이터베이스 플랫폼입니다. 이 플랫폼은 소스 오브 트루스(source-of-truth) 데이터를 PostgreSQL에 저장하지만, 이를 Elasticsearch로 자동 복제합니다. 이는 동일한 단순 서버리스 API 뒤에서 Postgres의 기능(ACID 트랜잭션, 조인, 제약 조건 등)과 Elasticsearch의 기능(BM25 전체 텍스트 검색, 벡터 검색, 하이브리드 검색)을 모두 제공한다는 의미입니다. 이는 대부분의 AI 애플리케이션이 필요로 하는 기능을 포괄하며, PostgreSQL과 Elasticsearch를 기반으로 하기 때문에 신뢰성이 높고 확장 가능합니다.
Xata는 TypeScript/JavaScript와 Python용 클라이언트 SDK를 보유하고 있으며, GitHub, Vercel, Netlify 같은 플랫폼과의 내장 통합 기능도 제공합니다.
AI 분야에서, 여기서 발표된 LangChain 통합 외에도 Xata는 '사용자 데이터 기반 ChatGPT' 사용 사례에 대해 OpenAI와의 깊은 통합 기능을 제공합니다.
통합 기능들
오늘부로 다음과 같은 통합 기능들이 사용할 수 있습니다:
- LangChain에서 벡터 스토어로 사용되는 Xata.
이를 통해 임베딩이 포함된 문서를 Xata 테이블에 저장하고 해당 문서들에 대해 벡터 검색을 수행할 수 있습니다. 이 통합은 새로 GA(General Availability)된 Python SDK를 활용합니다. 또한, 최대 성능을 위해 Xata 컬럼에 표현되는 메타데이터 필터링을 지원합니다.
- LangChain.js에서 벡터 스토어로 사용되는 Xata. 이는 Python 통합과 동일하며, TypeScript/JavaScript 애플리케이션을 위한 것입니다.
- LangChain에서 메모리 스토어로 사용되는 Xata. 이를 통해 AI 채팅 세션의 채팅 메시지 기록을 Xata에 저장할 수 있으며, 이는 LLM(대규모 언어 모델) 애플리케이션의 “메모리” 역할을 수행하게 합니다. 메시지는
- LangChain.js에서 메모리 스토어로 사용되는 Xata. 이는 Python 통합과 동일하며, TypeScript/JavaScript를 위한 것입니다.
각 통합에는 위에 링크된 문서 페이지에 하나 또는 두 개의 코드 예제가 제공됩니다.
이 네 가지 통합만으로도 Xata는 이미 LangChain을 위한 가장 포괄적인 데이터 솔루션 중 하나가 되었으며, 저희는 이제 막 시작하는 단계입니다! 가까운 미래에는 Xata 키워드 및 하이브리드 검색을 위한 사용자 지정 리트리버(custom retrievers)와 Xata Ask AI 엔드포인트를 추가할 계획입니다.
예시: 메모리를 사용한 대화형 질의응답 (Conversational Q&A)
각 LangChain 통합에는 최소 하나의 간소화된 코드 예제가 제공되지만, 본 블로그 게시물에서는 Xata를 벡터 스토어와 메모리 스토어 두 가지 모두로 사용하는 더 복잡한 예시를 살펴보겠습니다. 이 애플리케이션은 “데이터와 대화하기(chat with your data)” 사용 사례를 구현하며 후속 질문을 할 수 있도록 합니다. 전체 코드는 이 리포지토리에서 찾을 수 있으며, LangChain + Xata 애플리케이션의 스타터 키트로도 사용할 수 있습니다.
여기 예시 애플리케이션은 TypeScript로 작성되었지만, Python LangChain을 사용한 유사한 예시는 이 주피터 노트북에서 찾을 수 있습니다.
코드의 주요 부분은 다음과 같습니다:
import * as dotenv from "dotenv";
import { XataVectorSearch } from "langchain/vectorstores/xata";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { Document } from "langchain/document";
먼저 필요한 라이브러리들을 불러옵니다.
import { ConversationalRetrievalQAChain } from "langchain/chains";
import { BufferMemory } from "langchain/memory";
import { XataChatMessageHistory } from "langchain/stores/message/xata";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { getXataClient } from "./xata.ts";
dotenv.config();
const client = getXataClient();
/* 벡터 스토어 생성 */
docs 테이블을 사용하며, OpenAIEmbeddings를 사용하여 임베딩을 생성하고, 이를 기반으로 XataVectorSearch 객체를 초기화하여 벡터 스토어를 만듭니다.
const table = "docs";
const embeddings = new OpenAIEmbeddings();
const vectorStore = new XataVectorSearch(embeddings, { client, table });
/* 문서들을 벡터 스토어에 추가 */
Xata가 PostgreSQL 기반의 Serverless Data 플랫폼이며, 내장된 벡터 타입을 제공하여 벡터를 저장하고 쿼리할 수 있다는 내용 등의 문서를 준비합니다.
const docs = [
new Document({
pageContent: "Xata is a Serverless Data platform based on PostgreSQL",
}),
new Document({
pageContent:
"Xata offers a built-in vector type that can be used to store and query vectors",
}),
new Document({
pageContent: "Xata includes similarity search",
}),
];
const ids = await vectorStore.addDocuments(docs);
await new Promise((r) => setTimeout(r, 2000));
/* 채팅 메모리 스토어 생성 */
XataChatMessageHistory를 사용하여 대화 기록을 저장할 BufferMemory 객체를 만듭니다. 이 과정에서 세션 ID와 테이블 이름이 지정됩니다.
const memory = new BufferMemory({
chatHistory: new XataChatMessageHistory({
table: "memory",
sessionId: new Date().toISOString(),
client,
createTable: false,
}),
memoryKey: "chat_history",
});
/* 질문에 답하는 데 사용할 LLM 초기화 */
ChatOpenAI를 사용하여 언어 모델(LLM)을 초기화합니다.
const model = new ChatOpenAI({});
/* 체인 생성 및 실행 */
최종적으로 ConversationalRetrievalQAChain을 생성합니다. 이 체인은 LLM, 벡터 검색기(retriever), 그리고 메모리(memory)를 연결하여 대화형 질의응답 기능을 수행할 수 있도록 합니다.
const chain = ConversationalRetrievalQAChain.fromLLM(
model,
vectorStore.asRetriever(),
{
memory,
}
);
/* 첫 번째 질문 실행 */
"Xata란 무엇인가요?"라는 질문을 던지고 결과를 콘솔에 출력합니다.
const question = "What is Xata?";
const res = await chain.call({ question });
console.log("Question: ", question);
console.log(res);
/* 후속 질문 실행 */
첫 번째 답변을 바탕으로 "벡터 검색이 가능한가요?"라는 후속 질문을 던지고 결과를 출력합니다.
const followUpQ = "Can it do vector search?";
const followUpRes = await chain.call({
question: followUpQ,
});
console.log("Follow-up question: ", followUpQ);
console.log(followUpRes);
/* 벡터 스토어와 메모리 스토어 모두 정리 */
await vectorStore.delete({ ids });
await memory.clear();
이것을 조각별로 나누어 무엇을 하는지 살펴보겠습니다:
먼저, Xata를 벡터 스토어(vector store)로 사용합니다. 이 벡터 스토어에는 몇 가지 샘플 문서를 인덱싱하지만, 실제 애플리케이션에서는 수만 개의 문서를 인덱싱할 수 있습니다. 이 문서들은 챗봇이 사용자 질문에 대한 답변을 찾는 데 사용할 문서들입니다. 여기서는 보여주지 않았지만, 이러한 문서에 사용자 정의 메타데이터(metadata) 열을 추가하는 것도 가능합니다. 예시는 통합 페이지에서 확인할 수 있습니다.
/* 벡터 스토어 생성 */
const table = "docs";
const embeddings = new OpenAIEmbeddings();
const vectorStore = new XataVectorSearch(embeddings, { client, table });
/* 벡터 스토어에 문서 추가 */
const docs = [
new Document({
pageContent: "Xata is a Serverless Data platform based on PostgreSQL",
}),
new Document({
pageContent:
"Xata offers a built-in vector type that can be used to store and query vectors",
}),
new Document({
pageContent: "Xata includes similarity search",
}),
];
const ids = await vectorStore.addDocuments(docs);
다음으로, 다시 한번 Xata 기반의 채팅 메모리 스토어(chat memory store)를 생성합니다. 이것은 챗봇과 사용자가 교환한 메시지들을 Xata 테이블에 저장합니다. 각 대화에는 세션 ID(session ID)가 부여되며, 이 ID는 대화의 이전 메시지를 검색하는 데 사용되어 컨텍스트(context)가 손실되지 않도록 합니다.
/* 채팅 메모리 스토어 생성 */
const memory = new BufferMemory({
chatHistory: new XataChatMessageHistory({
table: "memory",
sessionId: new Date().toISOString(), // 또는 대화의 다른 고유 식별자
client,
createTable: false,
}),
memoryKey: "chat_history",
});
다음으로 모델을 질의하기 위한 클라이언트를 초기화합니다. 이 경우 OpenAI ChatGPT API를 사용합니다:
/* 질문에 답변하는 데 사용할 LLM 초기화 */
const model = new ChatOpenAI({});
마지막으로, 이 모든 것을 대화형 질의응답 체인(conversational QA chain)에 함께 배치합니다:
/* 체인 생성 */
const chain = ConversationalRetrievalQAChain.fromLLM(
model,
vectorStore.asRetriever(),
{
memory,
}
);
예제가 실행되는 동안 Xata UI를 통해 데이터를 살펴보면 docs 테이블과 memory 테이블 두 개가 보일 것입니다. docs 테이블은 벡터 스토어의 문서들로 채워지며, content 컬럼과 타입이 vector인 embedding 컬럼을 가집니다:
memory 테이블은 사용자 및 AI로부터 받은 질문과 답변으로 채워집니다:
콘텐츠 해커톤 (Content hackathon)
런칭 주간의 일환으로, Xata는 앱 제작, 블로그 작성, 영상 녹화 등을 통해 상품(prizes)과 기념품(swag)을 받을 수 있는 콘텐츠 해커톤도 개최합니다. 자세한 내용은 런칭 주간 블로그 게시물을 참고해 주세요.
Xata와 LangChain을 연동하는 데 질문이나 아이디어가 있거나 도움이 필요한 경우, Discord에 참여하거나 Twitter를 통해 문의해 주십시오.