Trong hướng dẫn này, chúng tôi tập trung vào cách xây dựng công cụ CLI trả lời câu hỏi bằng Dewy và LangChain.js. Dewy là cơ sở tri thức nguồn mở giúp các nhà phát triển tổ chức và truy xuất thông tin một cách hiệu quả. LangChain.js là một framework giúp đơn giản hóa việc tích hợp các mô hình ngôn ngữ lớn (LLM) vào các ứng dụng. Bằng cách kết hợp khả năng quản lý kiến thức của Dewy với tích hợp LLM của LangChain.js, bạn có thể tạo các công cụ trả lời các truy vấn phức tạp với thông tin chính xác và phù hợp.
Hướng dẫn này hướng dẫn bạn cách thiết lập môi trường, tải tài liệu vào Dewy và sử dụng LLM thông qua LangChain.js để trả lời các câu hỏi dựa trên dữ liệu được lưu trữ. Nó được thiết kế dành cho các kỹ sư muốn nâng cao dự án của họ bằng các chức năng trả lời câu hỏi nâng cao.
Dewy là một cơ sở kiến thức OSS được thiết kế để hợp lý hóa cách các nhà phát triển lưu trữ, sắp xếp và truy xuất thông tin. Tính linh hoạt và dễ sử dụng của nó làm cho nó trở thành một lựa chọn tuyệt vời cho các nhà phát triển muốn xây dựng các ứng dụng dựa trên tri thức.
Mặt khác, LangChain.js là một framework mạnh mẽ cho phép các nhà phát triển tích hợp LLM vào ứng dụng của họ một cách liền mạch. Bằng cách kết hợp quản lý kiến thức có cấu trúc của Dewy với khả năng LLM của LangChain.js, các nhà phát triển có thể tạo ra các hệ thống trả lời câu hỏi phức tạp có thể hiểu và xử lý các truy vấn phức tạp, đưa ra câu trả lời chính xác và phù hợp với ngữ cảnh.
Mục đích của chúng tôi là xây dựng một tập lệnh CLI trả lời câu hỏi đơn giản nhưng mạnh mẽ. Tập lệnh này sẽ cho phép người dùng tải tài liệu vào cơ sở kiến thức Dewy và sau đó sử dụng LLM, thông qua LangChain.js, để trả lời các câu hỏi dựa trên thông tin được lưu trữ trong Dewy. Hướng dẫn này sẽ hướng dẫn bạn thực hiện quy trình, từ thiết lập môi trường đến triển khai tập lệnh CLI.
Bạn sẽ tìm hiểu cách sử dụng LangChain để xây dựng một ứng dụng trả lời câu hỏi đơn giản và cách tích hợp Dewy làm nguồn kiến thức, cho phép ứng dụng của bạn trả lời các câu hỏi dựa trên các tài liệu cụ thể mà bạn cung cấp.
Trước khi đi sâu vào hướng dẫn, hãy đảm bảo bạn đáp ứng được các điều kiện tiên quyết sau:
Mã cuối cùng cho ví dụ này có sẵn trong kho Dewy nếu bạn muốn tiếp tục.
Đầu tiên, tạo một thư mục cho dự án TypeScript CLI và thay đổi thư mục
mkdir dewy_qa cd dewy_qa
Với thư mục đã được thiết lập, bạn có thể cài đặt TypeScript và khởi tạo dự án:
npm init -y npm i typescript --save-dev npx tsc --init
Tùy thuộc vào môi trường của bạn, bạn có thể cần thực hiện một số thay đổi đối với cấu hình TypeScript của mình. Đảm bảo rằng tsconfig.json
của bạn trông giống như sau:
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }
Bây giờ bạn đã sẵn sàng tạo ứng dụng CLI. Để giữ cho mã không quá lộn xộn, hãy sắp xếp mã thành nhiều thư mục với bố cục sau
dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts
Mỗi lệnh sẽ được triển khai trong thư mục commands
và mã chia sẻ sẽ nằm trong thư mục utils
. Điểm vào ứng dụng CLI là tệp index.ts
.
Bắt đầu với phiên bản "hello world" đơn giản của index.ts
- bạn sẽ bắt đầu điền nó trong phần tiếp theo
#!/usr/bin/env ts-node-script console.log("hello world");
Để xác minh môi trường được thiết lập chính xác, hãy thử chạy lệnh sau - bạn sẽ thấy "hello world" được in trong bảng điều khiển:
npx ts-node index.ts
Thay vì phải gõ lệnh rất dài này mỗi lần, hãy tạo một mục trong package.json
cho lệnh. Điều này sẽ giúp chúng ta nhớ cách gọi CLI và giúp cài đặt dưới dạng lệnh dễ dàng hơn:
{ ... "bin": { "dewy_qa": "./index.ts" } ... }
Bây giờ bạn có thể chạy tập lệnh của mình với npm exec dewy_qa
hoặc npm link
gói và chạy nó dưới dạng dewy_qa
Tải tài liệu bằng cách thiết lập ứng dụng khách Dewy. Bước đầu tiên là thêm một số phụ thuộc vào dự án. Đầu tiên là dewy-ts
, thư viện máy khách dành cho Dewy. Thứ hai là commander
, nó sẽ giúp chúng ta xây dựng một ứng dụng CLI với tính năng phân tích cú pháp đối số, các lệnh phụ, v.v. Cuối cùng, chalk
để làm cho lời nhắc có nhiều màu sắc hơn.
npm install dewy-ts commander chalk
Tiếp theo, thực hiện logic của lệnh tải. Bạn sẽ thực hiện việc này trong một tệp riêng biệt có tên là commands/load.ts
. Tệp này triển khai một hàm có tên là load
, hàm này yêu cầu một URL và một số tùy chọn bổ sung - hàm này sẽ được kết nối với CLI trong phần sau.
Dewy khiến việc tải tài liệu trở nên cực kỳ đơn giản - chỉ cần thiết lập ứng dụng khách và gọi addDocument
kèm theo URL của tệp bạn muốn tải. Dewy đảm nhiệm việc trích xuất nội dung của tệp PDF, chia chúng thành các phần có kích thước phù hợp để gửi tới LLM và lập chỉ mục cho chúng để tìm kiếm ngữ nghĩa.
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}`)); } }
Bạn có thể nhận thấy rằng một số hàm đã được nhập từ ../utils/colors
. Tệp này chỉ thiết lập một số trợ giúp cho đầu ra của bảng điều khiển tô màu - hãy đặt nó vào utils
để có thể sử dụng ở nơi khác:
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);
Với khả năng tải tài liệu vào Dewy, đã đến lúc tích hợp LangChain.js để sử dụng LLM nhằm trả lời các câu hỏi. Bước này bao gồm việc thiết lập LangChain.js để truy vấn cơ sở kiến thức Dewy và xử lý kết quả bằng LLM để tạo ra câu trả lời.
Để bắt đầu, hãy cài đặt một số gói bổ sung - langchain
và openai
để sử dụng API OpenAI làm LLM:
npm install dewy-langchain langchain @langchain/openai openai
Lệnh này khá dài, vì vậy chúng ta sẽ xem qua một số phần của nó trước khi kết hợp chúng lại
Điều đầu tiên cần thiết lập là Dewy (như trước) và LLM. Một điểm khác biệt so với trước đây là dewy
được sử dụng để xây dựng DewyRetriever
: đây là loại đặc biệt được LangChain sử dụng để truy xuất thông tin như một phần của chuỗi. Bạn sẽ thấy cách sử dụng chó tha mồi chỉ trong một phút.
const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });
Đây là mẫu chuỗi hướng dẫn LLM cách nó hoạt động, với các phần giữ chỗ cho ngữ cảnh bổ sung sẽ được cung cấp khi "chuỗi" được tạo. Trong trường hợp này, LLM được hướng dẫn trả lời câu hỏi nhưng chỉ sử dụng thông tin được cung cấp. Điều này làm giảm xu hướng "ảo giác" của mô hình hoặc bịa ra một câu trả lời hợp lý nhưng sai. Các giá trị của context
và question
được cung cấp trong bước tiếp theo:
const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);
LangChain hoạt động bằng cách xây dựng các "chuỗi" hành vi kiểm soát cách truy vấn LLM và các nguồn dữ liệu khác. Ví dụ này sử dụng LCEL , cung cấp trải nghiệm lập trình linh hoạt hơn một số giao diện ban đầu của LangChain.
Sử dụng RunnableSequence
để tạo chuỗi LCEL. Chuỗi này mô tả cách tạo context
và giá trị question
: ngữ cảnh được tạo bằng cách sử dụng trình truy xuất được tạo trước đó và câu hỏi được tạo bằng cách chuyển qua đầu vào của bước. Các kết quả mà Dewy truy xuất được định dạng dưới dạng một chuỗi bằng cách chuyển chúng tới hàm formatDocumentsAsString
.
Chuỗi này thực hiện như sau:
DewyRetriever
và gán chúng cho context
và gán giá trị đầu vào của chuỗi cho question
.context
và question
. const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
Bây giờ chuỗi đã được xây dựng, hãy thực thi nó và xuất kết quả ra bàn điều khiển. Như bạn sẽ thấy, question
là một đối số đầu vào được cung cấp bởi người gọi hàm.
Việc thực thi chuỗi bằng chain.streamLog()
cho phép bạn xem từng đoạn phản hồi khi nó được trả về từ LLM. Vòng lặp xử lý luồng khá xấu, nhưng nó chỉ lọc các kết quả luồng thích hợp và ghi chúng vào STDOUT
(sử dụng console.log
nó sẽ thêm dòng mới sau mỗi đoạn).
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); } } }
Bây giờ bạn đã xem tất cả các phần, bạn đã sẵn sàng tạo lệnh query
. Lệnh này trông giống như lệnh load
trước đó, với một số thao tác nhập bổ sung.
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}`)); } }
Với việc tích hợp Dewy và LangChain.js, bước tiếp theo là xây dựng giao diện CLI. Sử dụng thư viện giống như commander
để tạo giao diện dòng lệnh thân thiện với người dùng, hỗ trợ các lệnh tải tài liệu vào Dewy và truy vấn cơ sở kiến thức bằng LangChain.js.
Đầu tiên, viết lại index.ts
để tạo các lệnh phụ load
và query
. Đối số --collection
xác định tài liệu sẽ được tải vào bộ sưu tập Dewy nào (Dewy cho phép bạn sắp xếp tài liệu thành các bộ sưu tập khác nhau, tương tự như các thư mục tệp). Đối số --dewy-endpoint
cho phép bạn chỉ định cách kết nối với Dewy - theo mặc định, một phiên bản chạy cục bộ trên cổng 8000
được giả định. Cuối cùng, đối số --openai_api_key
(mặc định là biến môi trường) định cấu hình API OpenAI:
#!/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);
OK, tất cả đã xong - thật dễ dàng phải không? Bạn có thể dùng thử bằng cách chạy lệnh:
dewy_qa load https://arxiv.org/pdf/2009.08553.pdf
Bạn sẽ thấy một cái gì đó như
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 }
Việc trích xuất nội dung của một tệp PDF lớn có thể mất một hoặc hai phút, vì vậy bạn sẽ thường thấy "ingest_state": "pending"
khi tải tài liệu mới lần đầu tiên.
Tiếp theo, hãy thử đặt một số câu hỏi:
dewy_qa query "tell me about RAG
Bạn sẽ thấy một cái gì đó như
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...
Bằng cách làm theo hướng dẫn này, bạn đã học được cách tạo CLI sử dụng Dewy để quản lý kiến thức và LangChain.js để xử lý câu hỏi và tạo câu trả lời. Công cụ này thể hiện ứng dụng thực tế của việc kết hợp cơ sở kiến thức có cấu trúc với khả năng phân tích của LLM, cho phép các nhà phát triển xây dựng các ứng dụng thông minh và phản hồi nhanh hơn.