使用LangChain处理非结构化数据 + Ruby:打造高效知识库应用解决方案

云信安装大师
90
AI 质量分
3 5 月, 2025
6 分钟阅读
0 阅读

使用LangChain处理非结构化数据 + Ruby:打造高效知识库应用解决方案

引言

在当今信息爆炸的时代,企业知识库中充斥着大量非结构化数据(如PDF、Word文档、网页内容等)。如何高效地处理和利用这些数据成为了一个技术挑战。本文将介绍如何使用LangChain框架结合Ruby语言,构建一个能够处理非结构化数据的知识库应用解决方案。

准备工作

环境要求

  1. Ruby 2.7+ 环境
  2. Bundler gem
  3. Python 3.8+ (用于运行LangChain)
  4. Redis (可选,用于缓存)

安装必要组件

代码片段
# Ruby相关依赖
gem install langchainrb httparty nokogiri

# Python相关依赖(如果你需要直接调用Python的LangChain)
pip install langchain unstructured python-dotenv

LangChain基础概念

LangChain是一个用于构建基于大语言模型(LLM)应用的框架,它提供了:

  1. 文档加载器:从各种来源加载非结构化数据
  2. 文本分割器:将大文档分割成适合处理的块
  3. 向量存储:将文本转换为向量并存储以便快速检索
  4. 检索链:基于用户查询检索相关信息

实现步骤

1. 创建Ruby项目结构

代码片段
mkdir knowledge_base_app
cd knowledge_base_app
bundle init

编辑Gemfile:

代码片段
source "https://rubygems.org"

gem "langchainrb"
gem "httparty"
gem "nokogiri"
gem "dotenv"

然后运行:

代码片段
bundle install

2. 配置环境变量

创建.env文件:

代码片段
OPENAI_API_KEY=your_openai_api_key_here
REDIS_URL=redis://localhost:6379/0 # 可选

3. 实现文档加载器(Ruby版)

创建document_loader.rb

代码片段
require 'langchain'
require 'httparty'
require 'nokogiri'
require 'dotenv'

Dotenv.load

class DocumentLoader
  def self.load_from_url(url)
    response = HTTParty.get(url)
    document = Nokogiri::HTML(response.body)

    # 提取主要内容,这里简单示例,实际应用中可能需要更复杂的逻辑
    {
      url: url,
      title: document.title,
      content: document.css('body').text.gsub(/\s+/, ' ').strip,
      metadata: {
        fetched_at: Time.now.iso8601,
        source: url
      }
    }
  end

  def self.load_from_file(file_path)
    # PDF和Word文件需要额外的处理库,这里简化示例
    {
      path: file_path,
      content: File.read(file_path),
      metadata: {
        loaded_at: Time.now.iso8601,
        source: file_path,
        file_size: File.size(file_path)
      }
    }
  end

  def self.split_text(text, chunk_size: 1000, overlap: 200)
    # 简单的文本分割实现,LangChain有更高级的实现

    chunks = []
    start = 0

    while start < text.length do
      end_pos = [start + chunk_size, text.length].min - 1
      chunk = text[start..end_pos]

      chunks << {
        text: chunk,
        metadata: {
          start_pos: start,
          end_pos: end_pos,
          overlap_with_next_chunk: [overlap, text.length - end_pos - 1].min > 0 ? true : false 
        }
      }

      start += (chunk_size - overlap)
    end

    chunks
  end

end

# 使用示例:
if __FILE__ == $0
  # URL加载示例
  puts "Loading from URL..."

=begin  
  从URL加载文档并分割的完整示例:

  1. url = "https://example.com"
  2. doc = DocumentLoader.load_from_url(url)
  3. chunks = DocumentLoader.split_text(doc[:content])
  4. chunks.each_with_index do |chunk, index|
       puts "Chunk #{index + 1}: #{chunk[:text][0..50]}..."
     end

  这将输出每个文本块的前50个字符作为预览。
=end

end  

4. LangChain集成与向量存储

创建knowledge_base.rb

代码片段
require 'langchain'
require 'dotenv'

Dotenv.load

class KnowledgeBase

=begin  
初始化知识库的关键组件:

1. llm - Language Model接口 (这里使用OpenAI)
2. embeddings - 文本嵌入模型 (将文本转换为向量) 
3. vectorstore - 向量存储 (保存和检索嵌入向量)

参数说明:
- llm_model_name: OpenAI模型名称,如"gpt-3.5-turbo"
- embeddings_model_name: OpenAI嵌入模型名称,如"text-embedding-ada-002" 
- redis_url: Redis连接URL(可选),用于缓存嵌入结果提高性能

最佳实践提示:
- Ada(v2)嵌入模型性价比高且效果不错,适合大多数应用场景 
- Redis缓存可以显著提高重复查询的响应速度  
=end

def initialize(llm_model_name:, embeddings_model_name:, redis_url: nil) 

@llm = Langchain::LLM::OpenAI.new(
api_key: ENV["OPENAI_API_KEY"],
llm_options:{model:"gpt-3.5-turbo"}
)

@embeddings = Langchain::Embeddings::OpenAI.new(
api_key:"your_openai_api_key",
model:"text-embedding-ada-002"
)

@vectorstore = if redis_url 
Langchain::Vectorsearch::Redis.new(
embeddings:@embeddings,
index_name:"knowledge_base",
redis_url:"redis://localhost", 
namespace:"kb"
) 
else 
Langchain::Vectorsearch::Memory.new(
embeddings:@embeddings,
index_name:"knowledge_base" 
)
end 

puts "Knowledge base initialized with #{redis_url ? 'Redis' : 'in-memory'} vector store"

end 

=begin  
添加文档到知识库的核心方法:

1. documents - Array of Hash格式的文档数据,每个文档应包含:
   - :content字段(必需) - String格式的文本内容 
   - :metadata字段(可选) - Hash格式的附加元数据 

工作流程:
a)对每个文档进行分块处理(如果内容过长)
b)为每个文本块生成嵌入向量 
c)将向量和元数据存入向量数据库 

注意事项:
- OpenAI API有速率限制,大量文档建议分批处理并添加延迟 
- metadata中的关键信息会被索引用于后续过滤查询  
=end  

def add_documents(documents, chunk_size:nil)

documents.each do |doc|
content_chunks = if chunk_size && doc[:content].size > chunk_size*2 
DocumentLoader.split_text(doc[:content],chunk_size:) 
else 
[{text:[doc[:content]],metadata:[doc.fetch(:metadata,{})]}]
end 

content_chunks.each do |chunk|
@vectorstore.add_texts(
texts:[chunk[:text]],
metadatas:[chunk[:metadata]]
)

puts "Added document chunk with #{chunk[:text].size} characters"
sleep(0.5) # Rate limiting protection for free tier accounts 

end 

puts "Processed document with #{content_chunks.size} chunks"

end 

puts "#{documents.size} documents added to knowledge base"

end 

=begin  
查询知识库的核心方法:

参数说明:
query:String -自然语言查询问题   
k_results:int -返回的最相关结果数量(default=3)

返回值:
Array of Hash结构的相关结果,每个包含:
:text字段 -匹配的文本内容   
:metadata字段 -关联的元数据   
:distance字段 -与查询的相关性分数(越小越相关)

底层原理:
a)将查询文本转换为嵌入向量   
b)在向量空间中找到最接近的k个文档片段   
c)按相关性排序返回结果  

优化建议:
- k_results根据实际需求调整平衡精度和性能   
- metadata可用于结果后处理和过滤   
=end  

def query(query,k_results=3)

results=[]

begin 

results=@vectorstore.similarity_search(
query:,k:k_results).map do |result|

{
text:[result.text],
metadata:[result.metadata],
distance:[result.distance]
}

end 

rescue=>e 

puts"Query failed:#{e.message}"
return[]

ensure 

return results unless results.empty?

puts"No results found for query:#{query}"
[]

end 

end  

#其他实用方法...

def generate_response(query,k_results=3,max_tokens=150)

results=self.query(query,k_results)

context=[results.map{|r|r[:text]}.join("\n\n")]

prompt="Based on the following context:\n#{context}\n\nAnswer this question:#{query}"

response=[]

begin  

response=[@llm.chat(messages:[{role:"user",content:[prompt]}])]

rescue=>e  

response=["Error generating response:#{e.message}"]

ensure  

return response.first ||"No response generated"

end  

end  

#命令行交互演示...  

if __FILE__==$0  

kb=[KnowledgeBase.new(
llm_model_name:"gpt-3.5-turbo",
embeddings_model_name:"text-embedding-ada-002",
redis_url:nil)]

sample_docs=[
{
content:"Ruby is a dynamic,open source programming language with a focus on simplicity and productivity.",
metadata:{
source:"ruby-lang.org",
type:"definition"}},
{
content:"LangChain is a framework for developing applications powered by language models.",
metadata:{
source:"langchain.com",
type:"definition"}}]

kb.add_documents(sample_docs)

loop do  

print"\nEnter your query(or 'exit' to quit):"
input=[gets.chomp]

break if input.downcase=="exit"

results=[kb.query(input)]

puts"\nTop results:\n#{'-'*20}"

results.each.with_index(1)do |result,i|  

puts"[#{i}]#{result[:text][0..100]}...#{'(distance:'}#{result[:distance].round(4)})\nSource:#{[result.dig(:metadata,:source)]}\n\n"

end  

response=[kb.generate_response(input)]

puts"\nGenerated answer:\n#{'-'*20}\n#{response}\n"

end  

puts"\nExiting knowledge base demo."

end   

Python桥接方案(可选)

如果你的项目需要直接使用Python版的LangChain功能,可以通过以下方式桥接:

创建python_bridge.py:

代码片段
import os
from langchain.document_loaders import UnstructuredFileLoader, WebBaseLoader

def load_file(file_path):
    """Python实现的文件加载器"""
    loader = UnstructuredFileLoader(file_path)
    return loader.load()

def load_web(url):
    """Python实现的网页加载器"""
    loader = WebBaseLoader(url)
    return loader.load()

然后在Ruby中调用:

代码片段
def load_with_python(file_path)
 system("python python_bridge.py load_file #{file_path}")
 # ...处理返回结果...
end  

Ruby与Python集成的完整示例

创建一个完整的知识库应用流程:

代码片段
require './document_loader'
require './knowledge_base'

# Step1:初始化知识库(使用Redis缓存)
kb = KnowledgeBase.new(
 llm_model_name:"gpt-4",
 embeddings_model_name:"text-embedding-ada-002", 
 redis_url:"redis://localhost")

# Step2:加载多种来源的数据源(演示混合来源)
documents=[
 DocumentLoader.load_from_url("https://en.wikipedia.org/wiki/Ruby_(programming_language)"),
 DocumentLoader.load_from_file("path/to/your/document.pdf") #假设已转换文本

] + Dir.glob("docs/*.{txt,md}").map{|f|DocumentLoader.load_from_file(f)}

# Step3:添加到知识库(自动分块)
kb.add_documents(documents,chunk_size:)2000)

# Step4:交互式查询演示(真实应用可对接API)
loop do

 print"\nAsk about your documents(type'exit'to quit):"
 question=[gets.chomp]
 break if question.downcase=="exit"
 puts"\nProcessing..."

 #获取最相关的3个片段

 results=[kb.query(question)]
 puts"\nFound #{results.size}relevant passages:\n\n"
 results.each.with_index(1){|r,i|puts"[#{i}]Source:#{[r.dig(:metadata,:source)]}\n#{['-'*40]}\n#{r[:text]}\n\n"}


 #生成整合回答

 answer=[kb.generate_response(question)]
 puts"\Generated Answer:\#{['='*40]}\#{answer}\#


 ensure 

 puts"\Session saved."if defined?(kb)& & kb.save_to_disk("knowledge_base.dat")

}

常见问题解决

Q1:LangChain与Ruby集成时出现API错误?

解决方案:

检查以下方面:

代码片段
1.OpenAI API密钥是否正确设置?
2.Ruby gem版本是否兼容?(运行`bundle update langchainrb`)
3.Rate limit是否超限?(添加`sleep`间隔请求或升级API计划)

Q2:Rails应用中如何集成这个方案?

推荐做法:

代码片段
创建一个Rails服务对象:

class KnowledgeBaseService

 def initialize(user:) 

 @user_id=[user.id]
 @kb=[KnowledgeBase.new(...)]
 @namespace="user_#{@user_id}"
 end 

 def ask(question:) 

 Rails.cache.fetch("kb_answer/#{@namespace}/#{Digest::MD5}.hexdigest",expires_in:)12.hours){
 @kb.query([question])
 } 

 rescue=>e 

 Rails.logger.error("KB query failed:#{e.message}")
 [] 

 ensure 

 nil 

 end 


这样可以在控制器中简单调用:

def show

 @answers=[KnowledgeBaseService.new(current_user).ask(params):question]]
 render'show'

 end 


注意事项:

•考虑异步处理大文档上传以避免请求超时(Sidekiq等后台任务方案非常适合此场景)。
•为用户提供上传进度反馈。
•实施合理的清理策略防止存储膨胀。

总结

本文介绍了如何利用LangChain和Ruby构建强大的知识库应用系统。关键要点包括:

代码片段
✓混合语言架构的优势(Ruby业务逻辑+Python AI能力)。
✓高效的非结构化数据处理流程。
✓实用的代码示例可直接集成到项目中。
✓生产环境部署的最佳实践建议。

进一步优化方向:

代码片段
•实现增量更新机制而非全量重建索引。添加用户反馈循环改进结果质量。扩展支持更多文件类型(Markdown、Excel等)。
•集成权限系统控制访问敏感内容。
•添加对话历史上下文理解能力。这些增强功能将使您的解决方案更加完善和实用!
原创 高质量