Android 开发者 2024年10月11日
实例详解 | 借助 Langchain 和 Gemma 2 构建 RAG 应用
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文通过实例详解,展示如何结合 LangChain 和 Gemma 2 构建一个功能强大的 RAG 应用。该实例涵盖从基础配置到高级实现的各个方面,为您的项目提供实用的参考。本文将为您介绍如何使用 LangChain、NestJS 和 Gemma 2 构建关于 PDF 格式 Angular 书籍的 RAG 应用。接着,HTMX 和 Handlebar 模板引擎将响应呈现为列表。应用使用 LangChain 及其内置的 PDF 加载器来加载 PDF 书籍,并将文档拆分为小块。然后,LangChain 使用 Gemini 嵌入文本模型将文档表示为向量,并将向量持久化存储到向量数据库中。向量存储检索器为大语言模型 (LLM) 提供上下文,以便在其数据中查找信息,从而生成正确的响应。

🤔 **环境配置**:首先,需要设置一些环境变量,包括: - GROQ_API_KEY:Groq Cloud API 密钥,用于访问 Groq Cloud 服务。 - GROQ_MODEL:Groq 模型名称,例如 'gemma2-9b-it'。 - GEMINI_API_KEY:Gemini API 密钥,用于访问 Gemini 服务。 - GEMINI_TEXT_EMBEDDING_MODEL:Gemini 文本嵌入模型名称,例如 'text-embedding-004'。 - HUGGINGFACE_API_KEY:Huggingface API 密钥,用于访问 Huggingface 推理服务。 - HUGGINGFACE_EMBEDDING_MODEL:Huggingface 嵌入模型名称,例如 'BAAI/bge-small-en-v1.5'。 - QDRANT_URL:Qdrant 空间 URL,用于访问 Qdrant 向量数据库。 - QDRANT_APK_KEY:Qdrant API 密钥,用于访问 Qdrant 空间。 - PORT:应用端口号,默认为 3001。

🚀 **依赖安装**:使用 npm 命令安装所需的依赖项,包括: - @google/generative-ai:用于访问 Gemini 服务。 - @huggingface/inference:用于访问 Huggingface 推理服务。 - @langchain/community:LangChain 社区库,提供一些额外的功能。 - @langchain/core:LangChain 核心库,提供一些基础功能。 - @langchain/google-genai:LangChain Google Generative AI 库,提供一些与 Gemini 服务相关的功能。 - @langchain/groq:LangChain Groq 库,提供一些与 Groq 服务相关的功能。 - @langchain/qdrant:LangChain Qdrant 库,提供一些与 Qdrant 服务相关的功能。 - @nestjs/config:NestJS 配置库,用于加载环境变量。 - @nestjs/swagger:NestJS Swagger 库,用于生成 API 文档。 - @nestjs/throttler:NestJS 限流库,用于限制 API 请求速率。 - class-transformer:用于将对象转换为其他类型。 - class-validator:用于验证对象数据。 - compression:用于压缩响应数据。 - hbs:Handlebar 模板引擎,用于渲染响应数据。 - langchain:LangChain 库,用于构建 RAG 应用。 - pdf-parse:用于解析 PDF 文件。

🛠️ **应用配置**:创建 src/configs 文件夹,并在其中添加 configuration.ts 文件。该文件定义了应用的配置,包括端口号、Groq 配置、Gemini 配置、Huggingface 配置和 Qdrant 配置。

🌐 **Groq 模块**:使用 NestJS 命令创建 Groq 模块、控制器和服务。

🧠 **向量存储模块**:使用 NestJS 命令创建向量存储模块。该模块负责将文档表示为向量并存储到向量数据库中。

📈 **可配置的嵌入模型**:定义一个名为 'createTextEmbeddingModel' 的工厂方法,根据嵌入模型标志创建嵌入模型。该方法支持 Gemini 文本嵌入模型和 Huggingface 推理嵌入模型。

🗄️ **可配置的向量存储检索器**:定义一个名为 'VectorDatabase' 的接口,该接口提供初始化向量存储和获取向量存储检索器的方法。实现了两个向量存储服务:MemoryVectorDBService 和 QdrantVectorDBService。MemoryVectorDBService 将向量持久化存储到内存存储中,而 QdrantVectorDBService 将向量存储到 Qdrant 向量数据库中。

💬 **聊天模型**:定义 Groq 聊天模型,并提供一个名为 'GroqService' 的服务,用于执行查询并要求模型生成文本响应。

🧩 **模块导出**:将 Groq 聊天模型导出到其他模块使用。

🚀 **应用启动**:使用 NestJS 命令启动应用。

🔌 **API 接口**:定义 Groq 控制器,提供一个名为 'testChain' 的 API 接口,用于测试 Groq 聊天模型。

📚 **文档解析**:使用 LangChain 的 PDF 加载器加载 PDF 文件,并将文档拆分为小块。

🔍 **向量化**:使用 Gemini 嵌入文本模型将文档表示为向量。

💾 **向量存储**:将向量持久化存储到向量数据库中。

🧠 **检索器**:创建向量存储检索器,用于检索与查询相关的向量。

💬 **问答**:使用 LLM 生成响应,并将其呈现为列表。

🎨 **模板引擎**:使用 Handlebar 模板引擎渲染响应数据。

🚀 **响应呈现**:使用 HTMX 将响应呈现为列表。

🌐 **API 文档**:使用 Swagger 生成 API 文档。

🔐 **安全防护**:使用限流库限制 API 请求速率。

📝 **日志记录**:使用日志记录库记录应用运行时的信息。

📈 **性能优化**:使用缓存库缓存一些数据,以提高应用性能。

🧪 **单元测试**:编写单元测试,以确保应用代码的质量。

🤝 **代码复审**:进行代码复审,以确保代码的质量和可维护性。

🚀 **部署**:将应用部署到生产环境中。

监控**:监控应用运行时的状态,以确保应用的稳定性。

维护**:定期维护应用,以修复 bug 和添加新功能。

🤝 **社区支持**:加入 LangChain 社区,获取帮助和支持。

💻 **开源贡献**:为 LangChain 项目贡献代码,以改善其功能和性能。

👏 **未来展望**:探索 LangChain 的更多功能,例如: - 使用其他 LLM 模型。 - 支持其他数据源。 - 添加更多功能,例如: - 语义搜索。 - 文本摘要。 - 文本生成。

🎨 **创意应用**:将 LangChain 应用于各种场景,例如: - 构建智能问答系统。 - 构建知识库管理系统。 - 构建文档搜索系统。 - 构建内容推荐系统。 - 构建聊天机器人。

2024-10-11 17:31 北京

本文通过实例详解,展示如何结合 Langchain 和 Gemma 2 构建一个功能强大的 RAG 应用。该实例涵盖从基础配置到高级实现的各个方面,为您的项目提供实用的参考。

本文原作者:Connie Leung,谷歌开发者专家 (GDE),原文发布于:DEV Community

https://dev.to/railsstudent/build-a-rag-application-to-learn-angular-using-langchhtainjs-nestjs-htmx-and-gemma-2-5ggk


本文将为您介绍如何使用 LangChain、NestJS 和 Gemma 2 构建关于 PDF 格式 Angular 书籍的 RAG 应用。接着,HTMX 和 Handlebar 模板引擎将响应呈现为列表。应用使用 LangChain 及其内置的 PDF 加载器来加载 PDF 书籍,并将文档拆分为小块。然后,LangChain 使用 Gemini 嵌入文本模型将文档表示为向量,并将向量持久化存储到向量数据库中。向量存储检索器为大语言模型 (LLM) 提供上下文,以便在其数据中查找信息,从而生成正确的响应。



设置环境变量


PORT=3001GROQ_API_KEY=<GROQ API KEY>GROQ_MODEL=gemma2-9b-itGEMINI_API_KEY=<GEMINI API KEY>GEMINI_TEXT_EMBEDDING_MODEL=text-embedding-004HUGGINGFACE_API_KEY=<Huggingface API KEY>HUGGINGFACE_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5QDRANT_URL=<Qdrant URL>QDRANT_APK_KEY=<Qdrant API KEY>


访问 https://aistudio.google.com/app/apikey,登录帐号,创建新的 API 密钥。将 API 密钥替换为 GEMINI_API_KEY


访问 Groq Cloud: https://console.groq.com/,注册帐号并新建一个 API 密钥。将 API 密钥替换为 GROQ_API_KEY


访问 Huggingface: https://huggingface.co/join,注册帐号,创建新的访问令牌。将访问令牌替换为 HUGGINGFACE_API_KEY


访问 Qdrant: https://cloud.qdrant.io/,注册帐号,创建 Qdrant 空间。将网址替换为 QDRANT_URL。将 API 密钥替换为 QDRANT_API_KEY



安装依赖项


npm i -save-exact @google/generative-ai @huggingface/inference   @langchain/community @langchain/core @langchain/google-genai @langchain/groq @langchain/qdrant @nestjs/config @nestjs/swagger @nestjs/throttler class-transformer class-validator compression hbs langchain pdf-parse
npm i -save-exact –save-dev @commitlint/cli @commitlint/config-conventional husky lint-staged



定义应用的配置


创建 src/configs 文件夹并在其中添加 configuration.ts 文件。

export default () => ({ port: parseInt(process.env.PORT, 10) || 3000, groq: {   apiKey: process.env.GROQ_API_KEY || '',   model: process.env.GROQ_MODEL || 'gemma2-9b-it', }, gemini: {   apiKey: process.env.GEMINI_API_KEY || '',   embeddingModel: process.env.GEMINI_TEXT_EMBEDDING_MODEL || 'text-embedding-004', }, huggingface: {   apiKey: process.env.HUGGINGFACE_API_KEY || '',   embeddingModel: process.env.HUGGINGFACE_EMBEDDING_MODEL || 'BAAI/bge-small-en-v1.5', }, qdrant: {   url: process.env.QDRANT_URL || 'http://localhost:6333',   apiKey: process.env.QDRANT_APK_KEY || '', },});



创建 Groq 模块


生成 Groq 模块、控制器和服务。

nest g mo groqnest g s groq/application/groq --flatnest g co groq/presenters/http/groq --flat 


添加一个聊天模型


在模块中定义 Groq 配置类型,文件路径为 application/types/groq-config.type.ts。配置服务将配置值转换为自定义对象。

export type GroqConfig = { model: string; apiKey: string;};

添加自定义提供程序以提供 GroqChatModel 的实例。在 application/constants 文件夹下创建 groq.constant.ts 文件。

// application/constants/groq.constant.ts
export const GROQ_CHAT_MODEL = 'GROQ_CHAT_MODEL';
// application/providers/groq-chat-model.provider.ts
import { ChatGroq } from '@langchain/groq';import { Provider } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { GROQ_CHAT_MODEL } from '~groq/application/constants/groq.constant';import { GroqConfig } from '~groq/application/types/groq-config.type';
export const GroqChatModelProvider: Provider<ChatGroq> = { provide: GROQ_CHAT_MODEL, useFactory: (configService: ConfigService) => { const { apiKey, model } = configService.get<GroqConfig>('groq'); return new ChatGroq({ apiKey, model, temperature: 0.1, maxTokens: 2048, streaming: false, }); }, inject: [ConfigService],};


在控制器中测试 Groq 聊天模型


import { MessageContent } from '@langchain/core/messages';import { ChatPromptTemplate } from '@langchain/core/prompts';import { ChatGroq } from '@langchain/groq';import { Inject, Injectable } from '@nestjs/common';import { GROQ_CHAT_MODEL } from './constants/groq.constant';
@Injectable()export class GroqService { constructor(@Inject(GROQ_CHAT_MODEL) private model: ChatGroq) {}
async generateText(input: string): Promise<MessageContent> { const prompt = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant'], ['human', '{input}'], ]);
const chain = prompt.pipe(this.model); const response = await chain.invoke({ input, });
return response.content; }}

GroqService 服务有一个方法,用于执行查询并要求模型生成文本响应。

@Controller('groq')export class GroqController { constructor(private service: GroqService) {}
@Get() testChain(): Promise<MessageContent> { return this.service.generateText('What is Agentic RAG?'); }}


从模块导出聊天模型


import { Module } from '@nestjs/common';import { GroqChatModelProvider } from './application/providers/groq-chat-model.provider';import { GroqService } from './application/groq.service';import { GroqController } from './presenters/http/groq.controller';
@Module({ providers: [GroqChatModelProvider, GroqService], controllers: [GroqController], exports: [GroqChatModelProvider],})export class GroqModule {}



创建向量存储模块


nest g mo vectorStorenest g s application/vectorStore --flat


添加配置类型


application/types 文件夹下定义配置类型。


这是嵌入模型的配置类型。此应用同时支持 Gemini 文本嵌入模型和 Huggingface 推理嵌入模型。

// application/types/embedding-model-config.type.ts
export type EmbeddingModelConfig = { apiKey: string; embeddingModel: string;};

应用支持内存向量存储和 Qdrant 向量存储。因此,应用具有 Qdrant 配置。

// application/types/qdrant-database-config.type.ts
export type QdrantDatabaseConfig = { apiKey: string; url: string;};

此配置中存储了拆分后的文档、向量数据库类型和嵌入模型。

export type VectorDatabasesType = 'MEMORY' | 'QDRANT';
// application/types/vector-store-config.type.ts
import { Document } from '@langchain/core/documents';import { Embeddings } from '@langchain/core/embeddings';import { VectorDatabasesType } from './vector-databases.type';
export type VectorDatabaseFactoryConfig = { docs: Document<Record<string, any>>[]; type: VectorDatabasesType; embeddings: Embeddings;};
export type DatabaseConfig = Omit<VectorDatabaseFactoryConfig, 'type'>;


创建可配置的嵌入模型


export type EmbeddingModels = 'GEMINI_AI' | 'HUGGINGFACE_INFERENCE';
import { TaskType } from '@google/generative-ai';import { HuggingFaceInferenceEmbeddings } from '@langchain/community/embeddings/hf';import { Embeddings } from '@langchain/core/embeddings';import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';import { InternalServerErrorException } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { EmbeddingModelConfig } from '../types/embedding-model-config.type';import { EmbeddingModels } from '../types/embedding-models.type';
function createGeminiTextEmbeddingModel(configService: ConfigService) { const { apiKey, embeddingModel: model } = configService.get<EmbeddingModelConfig>('gemini'); return new GoogleGenerativeAIEmbeddings({ apiKey, model, taskType: TaskType.RETRIEVAL_DOCUMENT, title: 'Angular Book', });}
function createHuggingfaceInferenceEmbeddingModel(configService: ConfigService) { const { apiKey, embeddingModel: model } = configService.get<EmbeddingModelConfig>('huggingface'); return new HuggingFaceInferenceEmbeddings({ apiKey, model, });}
export function createTextEmbeddingModel(configService: ConfigService, embeddingModel: EmbeddingModels): Embeddings { if (embeddingModel === 'GEMINI_AI') { return createGeminiTextEmbeddingModel(configService); } else if (embeddingModel === 'HUGGINGFACE_INFERENCE') { return createHuggingfaceInferenceEmbeddingModel(configService); } else { throw new InternalServerErrorException('Invalid type of embedding model.'); }}

createGeminiTextEmbeddingModel 函数将实例化并返回 Gemini 文本嵌入模型。类似地,createHuggingfaceInferenceEmbeddingModel 将实例化并返回 Huggingface 推理嵌入模型。最后,createTextEmbeddingModel 函数是一个工厂方法,根据嵌入模型标志创建嵌入模型。



创建可配置的向量存储检索器


定义向量数据库服务接口

// application/interfaces/vector-database.interface.ts
import { VectorStore, VectorStoreRetriever } from '@langchain/core/vectorstores';import { DatabaseConfig } from '../types/vector-store-config.type';
export interface VectorDatabase { init(config: DatabaseConfig): Promise<void>; asRetriever(): VectorStoreRetriever<VectorStore>;}
import { VectorStore, VectorStoreRetriever } from '@langchain/core/vectorstores';import { Injectable, Logger } from '@nestjs/common';import { MemoryVectorStore } from 'langchain/vectorstores/memory';import { VectorDatabase } from '../interfaces/vector-database.interface';import { DatabaseConfig } from '../types/vector-store-config.type';
@Injectable()export class MemoryVectorDBService implements VectorDatabase { private readonly logger = new Logger(MemoryVectorDBService.name); private vectorStore: VectorStore;
async init({ docs, embeddings }: DatabaseConfig): Promise<void> { this.logger.log('MemoryVectorStoreService init called'); this.vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings); }
asRetriever(): VectorStoreRetriever<VectorStore> { return this.vectorStore.asRetriever(); }}

MemoryVectorDBService 实现了接口,将向量持久化存储到内存存储中,并返回向量存储检索器。

import { VectorStore, VectorStoreRetriever } from '@langchain/core/vectorstores';import { QdrantVectorStore } from '@langchain/qdrant';import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { QdrantClient } from '@qdrant/js-client-rest';import { VectorDatabase } from '../interfaces/vector-database.interface';import { QdrantDatabaseConfig } from '../types/qdrant-database-config.type';import { DatabaseConfig } from '../types/vector-store-config.type';
const COLLECTION_NAME = 'angular_evolution_collection';
@Injectable()export class QdrantVectorDBService implements VectorDatabase { private readonly logger = new Logger(QdrantVectorDBService.name); private vectorStore: VectorStore;
constructor(private configService: ConfigService) {}
async init({ docs, embeddings }: DatabaseConfig): Promise<void> { this.logger.log('QdrantVectorStoreService init called'); const { url, apiKey } = this.configService.get<QdrantDatabaseConfig>('qdrant'); const client = new QdrantClient({ url, apiKey }); const { exists: isCollectionExists } = await client.collectionExists(COLLECTION_NAME); if (isCollectionExists) { const isDeleted = await client.deleteCollection(COLLECTION_NAME); if (!isDeleted) { throw new InternalServerErrorException(`Unable to delete ${COLLECTION_NAME}`); } this.logger.log(`QdrantVectorStoreService deletes ${COLLECTION_NAME}. Result -> ${isDeleted}`); }
const size = (await embeddings.embedQuery('test')).length; const isSuccess = await client.createCollection(COLLECTION_NAME, { vectors: { size, distance: 'Cosine' }, });
if (!isSuccess) { throw new InternalServerErrorException(`Unable to create collection ${COLLECTION_NAME}`); }
this.vectorStore = await QdrantVectorStore.fromDocuments(docs, embeddings, { client, collectionName: COLLECTION_NAME, }); }
asRetriever(): VectorStoreRetriever<VectorStore> { return this.vectorStore.asRetriever(); }}

QdrantVectorDBService 实现了接口,将向量持久化存储到 Qdrant 向量数据库中,并返回向量存储检索器。

// application/vector-databases/create-vector-database.t
import { InternalServerErrorException } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { VectorDatabasesType } from '../types/vector-databases.type';import { MemoryVectorDBService } from './memory-vector-db.service';import { QdrantVectorDBService } from './qdrant-vector-db.service';
export function createVectorDatabase(type: VectorDatabasesType, configService: ConfigService) { if (type === 'MEMORY') { return new MemoryVectorDBService(); } else if (type === 'QDRANT') { return new QdrantVectorDBService(configService); } throw new InternalServerErrorException(`Invalid vector store type: ${type}`);}

函数将根据数据库类型实例化数据库服务。


从 Angular PDF 书籍创建文档块


将书籍复制到 assets 文件夹

import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 100,});
export async function loadPdf(path: string) { const loader = new PDFLoader(path);
const docs = await loader.load(); const splitDocs = await splitter.splitDocuments(docs); return splitDocs;}

loadPdf 函数使用 PDF 加载器来加载 PDF 文件,并将文档拆分为多个小块。

import { Embeddings } from '@langchain/core/embeddings';import { VectorStore, VectorStoreRetriever } from '@langchain/core/vectorstores';import { Inject, Injectable, Logger } from '@nestjs/common';import path from 'path';import { appConfig } from '~configs/root-path.config';import { ANGULAR_EVOLUTION_BOOK, TEXT_EMBEDDING_MODEL, VECTOR_DATABASE } from './constants/rag.constant';import { VectorDatabase } from './interfaces/vector-database.interface';import { loadPdf } from './loaders/pdf-loader';
@Injectable()export class VectorStoreService { private readonly logger = new Logger(VectorStoreService.name);
constructor( @Inject(TEXT_EMBEDDING_MODEL) embeddings: Embeddings, @Inject(VECTOR_DATABASE) private dbService: VectorDatabase,) { this.createDatabase(embeddings, this.dbService); }
private async createDatabase(embeddings: Embeddings, dbService: VectorDatabase) { const docs = await this.loadDocuments(); await dbService.init({ docs, embeddings }); }
private async loadDocuments() { const bookFullPath = path.join(appConfig.rootPath, ANGULAR_EVOLUTION_BOOK); const docs = await loadPdf(bookFullPath); this.logger.log(`number of docs -> ${docs.length}`); return docs; }
asRetriever(): VectorStoreRetriever<VectorStore> { this.logger.log(`return vector retriever`); return this.dbService.asRetriever(); }}

VectorStoreService 将 PDF 书籍存储到向量数据库中,并返回向量存储检索器。


将模块设为动态模块


import { DynamicModule, Module } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { TEXT_EMBEDDING_MODEL, VECTOR_DATABASE, VECTOR_STORE_TYPE } from './application/constants/rag.constant';import { createTextEmbeddingModel } from './application/embeddings/create-embedding-model';import { EmbeddingModels } from './application/types/embedding-models.type';import { VectorDatabasesType } from './application/types/vector-databases.type';import { createVectorDatabase, MemoryVectorDBService, QdrantVectorDBService } from './application/vector-databases';import { VectorStoreTestService } from './application/vector-store-test.service';import { VectorStoreService } from './application/vector-store.service';import { VectorStoreController } from './presenters/http/vector-store.controller';
@Module({ providers: [VectorStoreService, VectorStoreTestService, MemoryVectorDBService, QdrantVectorDBService], controllers: [VectorStoreController], exports: [VectorStoreService],})export class VectorStoreModule { static register(embeddingModel: EmbeddingModels, vectorStoreType: VectorDatabasesType): DynamicModule { return { module: VectorStoreModule, providers: [ { provide: TEXT_EMBEDDING_MODEL, useFactory: (configService: ConfigService) => createTextEmbeddingModel(configService, embeddingModel), inject: [ConfigService], }, { provide: VECTOR_STORE_TYPE, useValue: vectorStoreType, }, { provide: VECTOR_DATABASE, useFactory: (type: VectorDatabasesType, configService: ConfigService) => createVectorDatabase(type, configService), inject: [VECTOR_STORE_TYPE, ConfigService], }, ], }; }}

VectorStoreModule 是一个动态模块。嵌入模型和向量数据库均可自行配置。注册静态方法根据配置创建文本嵌入模块和向量数据库。



创建 RAG 模块


RAG 模块负责创建 LangChain 链,该链请求模型生成响应。

nest g mo ragTechBooknest g s ragTechBook/application/rag --flatnest g s ragTechBook/presenters/http/rag --flat 


创建 RAG 服务


// application/constants/prompts.constant.ts
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
const qaSystemPrompt = `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.
{context}`;
export const qaPrompt = ChatPromptTemplate.fromMessages([ ['system', qaSystemPrompt], new MessagesPlaceholder('chat_history'), ['human', '{question}'],]);
const contextualizeQSystemPrompt = `Given a chat history and the latest user questionwhich might reference context in the chat history, formulate a standalone questionwhich can be understood without the chat history. Do NOT answer the question,just reformulate it if needed and otherwise return it as is.`;
export const contextualizeQPrompt = ChatPromptTemplate.fromMessages([ ['system', contextualizeQSystemPrompt], new MessagesPlaceholder('chat_history'), ['human', '{question}'],]);

此常量文件存储了 LangChain 链的一些提示词。

import { StringOutputParser } from '@langchain/core/output_parsers';import { ChatGroq } from '@langchain/groq';import { contextualizeQPrompt } from '../constants/prompts.constant';
export function createContextualizedQuestion(llm: ChatGroq) { const contextualizeQChain = contextualizeQPrompt.pipe(llm).pipe(new StringOutputParser());
return (input: Record<string, unknown>) => { if ('chat_history' in input) { return contextualizeQChain; } return input.question; };}

该函数会创建一个链,该链可以在不依赖聊天历史记录的情况下提出问题。

import { BaseMessage } from '@langchain/core/messages';import { Runnable, RunnablePassthrough, RunnableSequence } from '@langchain/core/runnables';import { ChatGroq } from '@langchain/groq';import { Inject, Injectable } from '@nestjs/common';import { formatDocumentsAsString } from 'langchain/util/document';import { GROQ_CHAT_MODEL } from '~groq/application/constants/groq.constant';import { VectorStoreService } from '~vector-store/application/vector-store.service';import { createContextualizedQuestion } from './chain-with-history/create-contextual-chain';import { qaPrompt } from './constants/prompts.constant';import { ConversationContent } from './types/conversation-content.type';
@Injectable()export class RagService { private chat_history: BaseMessage[] = [];
constructor( @Inject(GROQ_CHAT_MODEL) private model: ChatGroq, private vectorStoreService: VectorStoreService,) {}
async ask(question: string): Promise<ConversationContent[]> { const contextualizedQuestion = createContextualizedQuestion(this.model); const retriever = this.vectorStoreService.asRetriever();
try { const ragChain = RunnableSequence.from([ RunnablePassthrough.assign({ context: (input: Record<string, unknown>) => { if ('chat_history' in input) { const chain = contextualizedQuestion(input); return (chain as Runnable).pipe(retriever).pipe(formatDocumentsAsString); } return ''; }, }), qaPrompt, this.model, ]);
const aiMessage = await ragChain.invoke({ question, chat_history: this.chat_history }); this.chat_history = this.chat_history.concat(aiMessage); if (this.chat_history.length > 10) { this.chat_history.shift(); }
return [ { role: 'Human', content: question, }, { role: 'Assistant', content: (aiMessage.content as string) || '', }, ]; } catch (ex) { console.error(ex); throw ex; } }}

RagService 服务非常简单。ask 方法将输入提交给链并输出响应。该方法从响应中提取内容,将聊天历史记录中人类和 AI 之间的聊天消息存储在内存中,并将对话返回给模板引擎进行渲染。


添加 RAG 控制器


import { IsNotEmpty, IsString } from 'class-validator';
export class AskDto { @IsString() @IsNotEmpty() query: string;}
@Controller('rag')export class RagController { constructor(private service: RagService) {}
@Post() async ask(@Body() dto: AskDto): Promise<string> { const conversation = await this.service.ask(dto.query); return toDivRow(conversation); }}

RAG 控制器将查询提交给链,获取结果,并将 HTML 代码发送回模板引擎进行渲染。


将模块导入 RAG 模块


import { Module } from '@nestjs/common';import { GroqModule } from '~groq/groq.module';import { VectorStoreModule } from '~vector-store/vector-store.module';import { RagService } from './application/rag.service';import { RagController } from './presenters/http/rag.controller';
@Module({ imports: [GroqModule, VectorStoreModule.register('GEMINI_AI', 'MEMORY')], providers: [RagService], controllers: [RagController],})export class RagTechBookModule {}


将 RagModule 导入 AppModule


import { RagTechBookModule } from '~rag-tech-book/rag-tech-book.module';
@Module({ imports: [ … other imports … RagTechBookModule, ], controllers: [AppController],})export class AppModule {}


修改应用控制器以渲染 Handlebar 模板


import { Controller, Get, Render } from '@nestjs/common';
@Controller()export class AppController { @Get() @Render('index') getHello(): Record<string, string> { return { title: 'Angular Tech Book RAG', }; }}

应用控制器通知 Handlebar 模板引擎渲染 index.hbs 文件。



HTMX 和 Handlebar 模板引擎


这是一个用于显示对话的简单界面。

default.hbs<!DOCTYPE html><html lang="en"> <head>   <meta charset="utf-8" />   <meta name="description" content="Angular tech book RAG powed by gemma 2 LLM." />   <meta name="author" content="Connie Leung" />   <meta name="viewport" content="width=device-width, initial-scale=1.0" />   <title>{{{ title }}}</title>   <style>     *, *::before, *::after {         padding: 0;         margin: 0;         box-sizing: border-box;     }</style>   <script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script> </head> <body class="p-4 w-screen h-screen min-h-full">   <script src="https://unpkg.com/htmx.org@2.0.1" integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/" crossorigin="anonymous"></script>   <div class="h-full grid grid-rows-[auto_1fr_40px] grid-cols-[1fr]">     {{> header }}     {{{ body }}}     {{> footer }}   </div> </body></html>

以上是具有页眉、页脚和正文的默认布局。正文最终显示的是 AI 与人类之间的对话。页眉部分则导入 Tailwind,用于设置 HTML 元素的样式,并导入 HTMX 来与服务器交互。

<div>   <div class="mb-2 p-1 border border-solid border-[#464646] rounded-lg">       <p class="text-[1.25rem] mb-2 text-[#464646] underline">Architecture</p>       <ul>           <li class="text-[1rem]">Chat Model: Groq</li>           <li class="text-[1rem]">LLM: Gemma 2</li>           <li class="text-[1rem]">Embeddings: Gemini AI Embedding / HuggingFace Embedding</li>           <li class="text-[1rem]">Vector Store: Memory Vector Store / Qdrant Vector Store</li>           <li class="text-[1rem]">Retriever: Vector Store Retriever</li>       </ul>   </div>   <div id="chat-list" class="mb-4 h-[300px] overflow-y-auto overflow-x-auto">       <div class="flex text-[#464646] text-[1.25rem] italic underline">           <span class="w-1/5 p-1 border border-solid border-[#464646]">Role</span>           <span class="w-4/5 p-1 border border-solid border-[#464646]">Result</span>       </div>   </div>   <form id="rag-form" hx-post="/rag" hx-target="#chat-list" hx-swap="beforeend swap:1s">       <div>           <label>               <span class="text-[1rem] mr-1 w-1/5 mb-2 text-[#464646]">Question: </span>               <input type="text" name="query" class="mb-4 w-4/5 rounded-md p-2"                   placeholder="Ask me something"                   aria-placeholder="Placeholder to ask question to RAG"></input>           </label>       </div>       <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white p-2 text-[1rem] flex justify-center items-center rounded-lg">           <span class="mr-1">Send</span><img class="w-4 h-4 htmx-indicator" src="/images/spinner.gif">       </button>   </form></div>

用户可以在文本框中输入问题,然后点击 "发送" 按钮。该按钮向 /rag 发出 POST 请求并将对话附加到列表中。


这个 LangChain RAG 应用到此就创建完成了,创建该应用时采用了 Gemma 2 模型,以生成响应。



资源


欢迎您查阅 Github 代码库,以获取更多实用资源:

https://github.com/railsstudent/nestjs-gemma2-rag-app





长按右侧二维码

查看更多开发者精彩分享


跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain Gemma 2 RAG 向量数据库 嵌入模型 问答系统 知识库 文档搜索 内容推荐
相关文章