paint-brush
Dewy 및 LangChain.js를 사용하여 질문 답변 CLI 생성by@kerinin
486
486

Dewy 및 LangChain.js를 사용하여 질문 답변 CLI 생성

Ryan11m2024/02/17
Read on Terminal Reader

이 튜토리얼에서는 Dewy와 LangChain.js를 사용하여 질문 답변 CLI 도구를 구축하는 방법에 중점을 두고 있습니다.
featured image - Dewy 및 LangChain.js를 사용하여 질문 답변 CLI 생성
Ryan HackerNoon profile picture
0-item
1-item
2-item

이 튜토리얼에서는 Dewy와 LangChain.js를 사용하여 질문 답변 CLI 도구를 구축하는 방법에 중점을 두고 있습니다. Dewy는 개발자가 정보를 효율적으로 구성하고 검색하는 데 도움이 되는 오픈 소스 지식 기반입니다. LangChain.js는 대규모 언어 모델(LLM)을 애플리케이션에 통합하는 과정을 단순화하는 프레임워크입니다. Dewy의 지식 관리 기능과 LangChain.js의 LLM 통합을 결합하면 정확하고 관련 있는 정보로 복잡한 쿼리에 답하는 도구를 만들 수 있습니다.


이 가이드는 환경 설정, Dewy에 문서 로드, LangChain.js를 통해 LLM을 사용하여 저장된 데이터를 기반으로 질문에 답하는 과정을 안내합니다. 고급 질문 답변 기능으로 프로젝트를 향상시키려는 엔지니어를 위해 설계되었습니다.

왜 Dewy와 LangChain.js를 사용하나요?

Dewy 는 개발자가 정보를 저장, 구성 및 검색하는 방식을 간소화하도록 설계된 OSS 지식 기반입니다. 유연성과 사용 편의성 덕분에 지식 기반 애플리케이션을 구축하려는 개발자에게 탁월한 선택이 됩니다.


반면 LangChain.js 는 개발자가 LLM을 자신의 애플리케이션에 원활하게 통합할 수 있게 해주는 강력한 프레임워크입니다. Dewy의 구조화된 지식 관리와 LangChain.js의 LLM 기능을 결합함으로써 개발자는 복잡한 쿼리를 이해하고 처리할 수 있는 정교한 질문 응답 시스템을 만들어 정확하고 상황에 맞는 답변을 제공할 수 있습니다.

목표

우리의 목표는 간단하면서도 강력한 질문 답변 CLI 스크립트를 구축하는 것입니다. 이 스크립트를 사용하면 사용자는 Dewy 지식 기반에 문서를 로드한 다음 LangChain.js를 통해 LLM을 사용하여 Dewy에 저장된 정보를 기반으로 질문에 답할 수 있습니다. 이 튜토리얼은 환경 설정부터 CLI 스크립트 구현까지의 프로세스를 안내합니다.


LangChain을 사용하여 간단한 질문 답변 애플리케이션을 구축하는 방법과 Dewy를 지식 소스로 통합하여 애플리케이션이 제공한 특정 문서를 기반으로 질문에 답할 수 있도록 하는 방법을 배우게 됩니다.

전제 조건

튜토리얼을 시작하기 전에 다음 전제조건이 충족되었는지 확인하십시오.

  • Typescript 프로그래밍에 대한 기본 지식
  • CLI 도구 개발에 대한 지식
  • 로컬 컴퓨터에서 실행 중인 Dewy의 복사본(도움이 필요하면 Dewy의 설치 지침을 참조하세요).

1단계: 프로젝트 설정

이 예제의 최종 코드는 Dewy 저장소에서 확인하실 수 있습니다.

먼저 TypeScript CLI 프로젝트용 디렉터리를 만들고 해당 디렉터리로 변경합니다.

 mkdir dewy_qa cd dewy_qa

디렉터리가 설정되면 TypeScript를 설치하고 프로젝트를 초기화할 수 있습니다.

 npm init -y npm i typescript --save-dev npx tsc --init

환경에 따라 TypeScript 구성을 일부 변경해야 할 수도 있습니다. tsconfig.json 이 다음과 같은지 확인하세요.

 { "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }

이제 CLI 애플리케이션을 생성할 준비가 되었습니다. 코드가 너무 지저분해지는 것을 방지하려면 다음 레이아웃을 사용하여 여러 디렉터리로 구성하세요.

 dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts

각 명령은 commands 디렉터리에 구현되며 공유 코드는 utils 디렉터리에 저장됩니다. CLI 애플리케이션의 진입점은 index.ts 파일입니다.

index.ts 의 간단한 "hello world" 버전으로 시작하세요. 다음 섹션에서 작성을 시작하게 됩니다.

 #!/usr/bin/env ts-node-script console.log("hello world");

환경이 올바르게 설정되었는지 확인하려면 다음 명령을 실행해 보십시오. 콘솔에 "hello world"가 인쇄되어야 합니다.

 npx ts-node index.ts

매번 이렇게 긴 명령을 입력하는 대신 package.json 에 해당 명령에 대한 항목을 만들어 보겠습니다. 이렇게 하면 CLI를 호출하는 방법을 기억하고 명령으로 설치하기가 더 쉬워집니다.

 { ... "bin": { "dewy_qa": "./index.ts" } ... }

이제 npm exec dewy_qa 또는 npm link 패키지를 사용하여 스크립트를 실행하고 dewy_qa 로 실행할 수 있습니다.

2단계: 문서 로드 구현

Dewy 클라이언트를 설정하여 문서를 로드하세요. 첫 번째 단계는 프로젝트에 일부 종속성을 추가하는 것입니다. 첫 번째는 Dewy용 클라이언트 라이브러리인 dewy-ts 입니다. 두 번째는 인수 구문 분석, 하위 명령 등을 사용하여 CLI 애플리케이션을 구축하는 데 도움이 되는 commander 입니다. 마지막으로, 프롬프트를 더욱 다채롭게 만들기 위해 chalk .

 npm install dewy-ts commander chalk

다음으로 load 명령의 논리를 구현합니다. 이 작업은 commands/load.ts 라는 별도의 파일에서 수행합니다. 이 파일은 URL과 몇 가지 추가 옵션이 필요한 load 라는 함수를 구현합니다. 이는 이후 섹션에서 CLI와 연결됩니다.


Dewy는 문서 로딩을 매우 간단하게 만듭니다. 클라이언트를 설정하고 로드하려는 파일의 URL로 addDocument 호출하기만 하면 됩니다. Dewy는 PDF 내용을 추출하여 LLM으로 보내기에 적합한 크기로 분할하고 의미 검색을 위해 색인화하는 작업을 담당합니다.

 import { Dewy } from 'dewy-ts'; import { success, error } from '../utils/colors'; export async function load(url: string, options: { collection: string, dewy_endpoint: string }): Promise<void> { console.log(success(`Loading ${url} into collection: ${options.collection}`)); try { const dewy = new Dewy({ BASE: options.dewy_endpoint }) const result = await dewy.kb.addDocument({ collection: options.collection, url }); console.log(success(`File loaded successfully`)); console.log(JSON.stringify(result, null, 2)); } catch (err: any) { console.error(error(`Failed to load file: ${err.message}`)); } }

일부 기능은 ../utils/colors 에서 가져온 것을 알 수 있습니다. 이 파일은 콘솔 출력 색상을 지정하기 위한 몇 가지 도우미를 설정합니다. 다른 곳에서 사용할 수 있도록 utils 에 넣습니다.

 import chalk from 'chalk'; export const success = (message: string) => chalk.green(message); export const info = (message: string) => chalk.blue(message); export const error = (message: string) => chalk.red(message);

3단계: 질문 답변 구현

Dewy에 문서를 로드할 수 있는 기능을 갖춘 이제 LangChain.js를 통합하여 질문에 답하기 위해 LLM을 활용할 때입니다. 이 단계에는 Dewy 지식 기반을 쿼리하고 LLM을 사용하여 결과를 처리하여 답변을 생성하도록 LangChain.js를 설정하는 작업이 포함됩니다.


시작하려면 몇 가지 추가 패키지( langchainopenai 를 설치하여 OpenAI API를 LLM으로 사용하세요.

 npm install dewy-langchain langchain @langchain/openai openai

이 명령은 좀 길기 때문에 마지막에 결합하기 전에 여러 부분을 살펴보겠습니다.

OpenAI 및 Dewy용 클라이언트 생성

가장 먼저 설정해야 할 것은 Dewy(이전과 마찬가지로)와 LLM입니다. 이전과의 한 가지 차이점은 dewy DewyRetriever 를 구축하는 데 사용된다는 것입니다. 이는 체인의 일부로 정보를 검색하기 위해 LangChain에서 사용하는 특수 유형입니다. 단 몇 분만에 리트리버가 어떻게 사용되는지 살펴보실 수 있습니다.

 const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });

LangChain 프롬프트 생성

이는 "체인"이 생성될 때 제공될 추가 컨텍스트에 대한 자리 표시자와 함께 LLM의 작동 방식을 지시하는 문자열 템플릿입니다. 이 경우 LLM은 질문에 답하도록 지시 받지만 제공된 정보만 사용합니다. 이렇게 하면 모델이 "환각"하거나 그럴듯하지만 잘못된 답을 만드는 경향이 줄어듭니다. contextquestion 의 값은 다음 단계에서 제공됩니다.

 const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);

체인 구축

LangChain은 LLM 및 기타 데이터 소스를 쿼리하는 방법을 제어하는 동작의 "체인"을 구축하여 작동합니다. 이 예에서는 일부 LangChain의 원래 인터페이스보다 더 유연한 프로그래밍 경험을 제공하는 LCEL을 사용합니다.


RunnableSequence 를 사용하여 LCEL 체인을 생성합니다. 이 체인은 contextquestion 값을 생성하는 방법을 설명합니다. 컨텍스트는 이전에 생성된 검색기를 사용하여 생성되고, 질문은 단계의 입력을 통과하여 생성됩니다. Dewy가 검색한 결과는 formatDocumentsAsString 함수에 연결하여 문자열 형식으로 지정됩니다.


이 체인은 다음을 수행합니다.

  1. DewyRetriever 사용하여 문서를 검색하고 이를 context 에 할당하고 체인의 입력 값을 question 에 할당합니다.
  2. contextquestion 변수를 사용하여 프롬프트 문자열의 형식을 지정합니다.
  3. 응답을 생성하기 위해 형식화된 프롬프트를 LLM에 전달합니다.
  4. LLM의 응답을 문자열로 형식화합니다.
 const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);

체인을 실행

이제 체인이 구성되었으므로 이를 실행하고 결과를 콘솔에 출력합니다. 보시다시피 question 함수 호출자가 제공하는 입력 인수입니다.


chain.streamLog() 사용하여 체인을 실행하면 LLM에서 반환되는 각 응답 청크를 볼 수 있습니다. 스트림 핸들러 루프는 다소 추악하지만 적절한 스트림 결과로 필터링하고 이를 STDOUT 에 쓰는 것뿐입니다( console.log 사용하면 각 청크 뒤에 줄바꿈이 추가되었을 것입니다).

 const stream = await chain.streamLog(question); // Write chunks of the response to STDOUT as they're received console.log("Answer:"); for await (const chunk of stream) { if (chunk.ops?.length > 0 && chunk.ops[0].op === "add") { const addOp = chunk.ops[0]; if ( addOp.path.startsWith("/logs/ChatOpenAI") && typeof addOp.value === "string" && addOp.value.length ) { process.stdout.write(addOp.value); } } }

명령으로 모두 함께 가져옵니다.

이제 모든 부분을 확인했으므로 query 명령을 만들 준비가 되었습니다. 이는 몇 가지 추가 가져오기를 제외하면 이전의 load 명령과 유사해 보일 것입니다.

 import { StringOutputParser } from "@langchain/core/output_parsers"; import { PromptTemplate } from "@langchain/core/prompts"; import { formatDocumentsAsString } from "langchain/util/document"; import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables"; import { ChatOpenAI } from "@langchain/openai"; import { Dewy } from 'dewy-ts'; import { DewyRetriever } from 'dewy-langchain'; import { success, error } from '../utils/colors'; export async function query(question: string, options: { collection: string, dewy_endpoint: string, openai_api_key: string }): Promise<void> { console.log(success(`Querying ${options.collection} collection for: "${question}"`)); try { const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection: options.collection }); const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`); const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]); const stream = await chain.streamLog(question); // Write chunks of the response to STDOUT as they're received console.log("Answer:"); for await (const chunk of stream) { if (chunk.ops?.length > 0 && chunk.ops[0].op === "add") { const addOp = chunk.ops[0]; if ( addOp.path.startsWith("/logs/ChatOpenAI") && typeof addOp.value === "string" && addOp.value.length ) { process.stdout.write(addOp.value); } } } } catch (err: any) { console.error(error(`Failed to query: ${err.message}`)); } }

4단계: CLI 구축

Dewy와 LangChain.js가 통합되면 다음 단계는 CLI 인터페이스를 구축하는 것입니다. commander 와 같은 라이브러리를 사용하여 Dewy에 문서를 로드하고 LangChain.js를 사용하여 지식 기반을 쿼리하는 명령을 지원하는 사용자 친화적인 명령줄 인터페이스를 만듭니다.


먼저 index.ts 다시 작성하여 loadquery 하위 명령을 만듭니다. --collection 인수는 문서를 로드해야 하는 Dewy 컬렉션을 결정합니다(Dewy를 사용하면 파일 폴더와 유사하게 문서를 다른 컬렉션으로 구성할 수 있습니다). --dewy-endpoint 인수를 사용하면 Dewy에 연결하는 방법을 지정할 수 있습니다. 기본적으로 포트 8000 에서 로컬로 실행되는 인스턴스가 가정됩니다. 마지막으로 --openai_api_key 인수(기본값은 환경 변수)는 OpenAI API를 구성합니다.

 #!/usr/bin/env ts-node-script import { Command } from 'commander'; import { load } from './commands/load'; import { query } from './commands/query'; const program = new Command(); program.name('dewy-qa').description('CLI tool for interacting with a knowledge base API').version('1.0.0'); const defaultOpenAIKey = process.env.OPENAI_API_KEY; program .command('load') .description("Load documents into Dewy from a URL") .option('--collection <collection>', 'Specify the collection name', 'main') .option('--dewy-endpoint <endpoint>', 'Specify the collection name', 'http://localhost:8000') .argument('<url>', 'URL to load into the knowledge base') .action(load); program .command('query') .description('Ask questions using an LLM and the loaded documents for answers') .option('--collection <collection>', 'Specify the collection name', 'main') .option('--dewy-endpoint <endpoint>', 'Specify the collection name', 'http://localhost:8000') .option('--openai-api-key <key>', 'Specify the collection name', defaultOpenAIKey) .argument('<question>', 'Question to ask the knowledge base') .action(query); program.parse(process.argv);

알았어, 다 끝났어. 쉽지 않았어? 다음 명령을 실행하여 시험해 볼 수 있습니다.

 dewy_qa load https://arxiv.org/pdf/2009.08553.pdf

당신은 다음과 같은 것을보아야합니다

 Loading https://arxiv.org/pdf/2009.08553.pdf into collection: main File loaded successfully { "id": 18, "collection": "main", "extracted_text": null, "url": "https://arxiv.org/pdf/2009.08553.pdf", "ingest_state": "pending", "ingest_error": null }

큰 PDF의 콘텐츠를 추출하는 데는 1~2분 정도 걸릴 수 있으므로 새 문서를 처음 로드할 때 "ingest_state": "pending" 표시되는 경우가 많습니다.

다음으로 몇 가지 질문을 해보세요.

 dewy_qa query "tell me about RAG

당신은 다음과 같은 것을보아야합니다

 Querying main collection for: "tell me about RAG" Answer: Based on the given context, RAG refers to the RAG proteins, which are involved in DNA binding and V(D)J recombination. The RAG1 and RAG2 proteins work together to bind specific DNA sequences known as RSS (recombination signal sequences) and facilitate the cutting and rearrangement of DNA segments during the process of V(D)J recombination...

결론

이 가이드를 따라 Dewy를 사용하여 지식을 관리하고 LangChain.js를 사용하여 질문을 처리하고 답변을 생성하는 CLI를 만드는 방법을 배웠습니다. 이 도구는 구조화된 지식 기반과 LLM의 분석 기능을 결합하여 개발자가 보다 지능적이고 반응이 빠른 애플리케이션을 구축할 수 있도록 하는 실제 적용 방법을 보여줍니다.

추가 자료 및 자료