Langchain2024大语言模型应用开发框架精选深度对比权威测评榜单推荐

2026-06-18阅读 0热度 0
ai 人工智能

聊到大型语言模型的应用落地,Langchain 几乎是一个绕不开的名字。它就像一个万能工具箱,把调用大模型、管理提示词、连接外部工具这些琐碎又关键的事情,都给你规整得明明白白。今天,咱们就深入聊聊这个框架,看看它到底解决了什么问题,以及在实际项目中,我们能拿它来做什么。

LLM的应用开发框架——Langchain

一. Langchain是什么

Langchain 官网文档:https://python.langchain.com/v0.2/docs/introduction/

LLM的应用开发框架——Langchain

LLM崛起出现了哪些需求?

随着大模型的能力越来越强,我们很快发现,光有模型本身是不够的。直接提问,得到的答案格式千奇百怪;想让它总结本书,提示词长得突破天际;希望它去网上查点实时信息,模型又无能为力。于是,一系列现实需求就浮出水面了:

  • 格式化输出:我们想要的是结构化的JSON、CSV,而不是大段大段的散文。
  • 处理长文本:比如要浓缩一本几十万字的小说,不能让模型一次性读进去,得想个办法。
  • 多步推理与API调用:有时候需要模型先算一算,再根据结果去查个东西,然后综合给出答案,这涉及多次、有依赖关系的API调用。
  • 打通外部世界:模型的知识是有截止日期的,让它能联网搜索、查询数据库,能力才能边界拓展。
  • 标准化与可切换性:今天是GPT-4,明天可能换用百川或GLM。代码应该能平滑迁移,而不是因为换了个模型就得重写一遍。

这些,正是Langchain试图一揽子解决的痛点。

二. Langchain支撑LLM的应用

下面,咱们就通过几个最典型的应用场景,看看Langchain具体是怎么落地的。

2.1 支持多种LLM

无论是OpenAI的GPT-4,Meta的LLaMA,还是国内的ChatGLM、百川,Langchain都提供了统一的接口来调用。这一点对于团队快速迭代模型,进行A/B测试非常关键。下面就以从Huggingface下载并使用百川2模型为例,展示一个自定义LLM类的写法。

from huggingface_hub import snapshot_download
from langchain.llms.base import LLM 
# 指定下载目录(在当前文件夹下)
snapshot_download(repo_id="baichuan-inc/Baichuan2-7B-Chat-4bits", local_dir="baichuan-inc/Baichuan2-7B-Chat-4bits")

class baichuan2_LLM(LLM):
    # 基于本地 Baichuan 自定义 LLM 类
    tokenizer: AutoTokenizer = None
    model: AutoModelForCausalLM = None
    def __init__(self, model_path: str, dtype = torch.bfloat16):
        # model_path: Baichuan-7B-chat模型路径
        # 从本地初始化模型
        super().__init__()
        print("正在从本地加载模型...")
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True,
        torch_dtype=dtype, device_map="auto")
        self.model.generation_config = GenerationConfig.from_pretrained(model_path)
        self.model = self.model.eval()
        print("完成本地模型的加载")

    def _call(self, prompt: str, stop: Optional[List[str]] = None,
             run_manager: Optional[CallbackManagerForLLMRun] = None,
             **kwargs: Any):
        # 重写调用函数
        messages = [
            {"role": "user", "content": prompt}
        ]
        # 重写调用函数
        response = self.model.chat(self.tokenizer, messages)
        return response 

    @property
    def _llm_type(self) -> str:
        return "baichuan2_LLM"

2.2 零样本少样本提示

一个经常被忽视的原则是:能用Prompt解决的,就别急着去微调模型。这能节省大量成本和时间。对于少样本甚至零样本的场景,Langchain提供了非常便捷的封装。通过给模型几个经典的“提问-回答”案例,就能引导它学会特定的回复风格。

from langchain.prompts.few_shot import FewShotPromptTemplate
examples = [
{
"question": "你好吗?",
"answer": "帅哥,我很好"
},
{
"question": "今天周几?",
"answer": "帅哥,今天周日"
},
{
"question": "天气好吗?",
"answer": "帅哥,是的,今天天气很不错"
}
]

    example_prompt = PromptTemplate(input_variables=["question", "answer"], template="提问: {question} n回答:{answer}")
    prompt = FewShotPromptTemplate(examples=examples,
                                   example_prompt=example_prompt,
                                   suffix="提问: {input} n",
                                   input_variables=["input"])
print(prompt.format(input="我怎么这么丑"))

# 这里相当于将前缀的这些少样本和当前问题全部放入llm,让其知道前后关系或者学习规则
print(llm.predict((prompt.format(input="我怎么这么丑"))))

2.3 文档问答

这是目前最成熟、应用最广的场景之一,典型代表就是 LangChain + ChatGLM 的本地知识库方案(GitHub - chatchat-space/Langchain-Chatchat)。整体逻辑非常清晰:

LLM的应用开发框架——Langchain

核心流程如下:

  • 加载:用Loader读取本地文档(PDF、TXT等)。
  • 分割:因为大模型有上下文窗口限制,需要把长文档切成固定大小的chunk。
  • 向量化:利用Embedding模型将这些chunk转换成向量。
  • 存库:将向量存入向量数据库(如Chroma、FAISS)。
  • 检索:对于用户的提问,同样向量化,然后去库里搜索最相似的几个chunk。
  • 生成:将检索回来的chunk和提问一起,打包成Prompt交给大模型,生成最终答案。

Langchain的强大之处在于它已经把这些步骤都模块化了。在这个过程中,有两个关键点决定了最终效果:Embedding模型的召回率LLM的回答能力。遇到bad case时,可以先定位到底是检索环节没找到相关信息,还是模型在理解上出了问题。下面是用《庆余年》文本做的一个简单演示,LLM和Embedding模型都来自Huggingface。

from langchain.indexes.vectorstore import VectorStoreIndexWrapper
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import TextLoader

def load_text_sa ve_index(file_path, index_name):
    loader = TextLoader(file_path)
    text_qyn = loader.load()

    splitter = RecursiveCharacterTextSplitter(
            chunk_size=100, # 分割出来的文本长度
            chunk_overlap=10, # 块之间的重叠文字
            length_function=len, # 计算每个块的长度
            add_start_index=True # 决定是否在metadata中包含每个块在原始文档中的起始位置
    )

    texts = splitter.split_documents(text_qyn)[:100]

    faiss_db = FAISS.from_documents(texts, hf_embeddings)
    faiss_db.sa ve_local(os.path.join(local_persist_path, index_name))
    print('faiss db sa ved !')

def load_index(index_name):
    index_path = os.path.join(local_persist_path, index_name)
    faiss_db = FAISS.load_local(index_path, embeddings = hf_embeddings, allow_dangerous_deserialization=True)
    index = VectorStoreIndexWrapper(vectorstore = faiss_db)
    return index

file_path = '庆余年.txt'
local_persist_path = './vector_store'

# load_text_sa ve_index(file_path, index_name='庆余年')
index = load_index(index_name='庆余年')
result = index.query("五竹是谁", llm=llm)
print(result)

2.4 搜索助手

这个功能让模型的“知识边界”无限扩展。通过Langchain的Agent模块,模型可以自主判断何时需要调用外部工具(比如搜索引擎),然后获取实时信息并整合答案。需要申请一个搜索API的key,例如SerpAPI。

from langchain.agents import initialize_agent
from langchain_community.agent_toolkits.load_tools import load_tools

tools = load_tools(['serpapi', 'llm-math'], llm=llm)
print(tools[1].name, tools[1].description)

agent = initialize_agent(tools, llm, agent = 'zero-shot-react-description', verbose=True)

agent.run('苹果的CEO,他10年后多少岁?')

2.5 文章总结

面对超长文章,一次性塞给模型必然导致内存溢出。Langchain提供了三种经典的“总结链”来处理这个问题:

  • Stuff:简单粗暴,将所有chunk拼在一起一次性总结。最直接,但受限于上下文窗口。
  • Map Reduce:先对每个chunk单独总结,然后再将所有总结结果进行第二次汇总。这种策略更通用,效果也更好。
  • Refine:迭代式总结。模型会逐个读取chunk,并在阅读每个新chunk时,对已有的总结进行修正和补充。适合需要不断迭代深化的场景。

下面是一个对网页新闻进行汇总的代码示例:

from langchain_community.document_loaders import UnstructuredURLLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains.summarize import load_summarize_chain
from langchain_core.prompts import PromptTemplate

def load_news(url):
    text_splitter = RecursiveCharacterTextSplitter(
# separators=['正文', '撰稿'],
                                                   chunk_size=300,
                                                   chunk_overlap=10)
    loader = UnstructuredURLLoader([url])
    data = loader.load_and_split(text_splitter)
    print(f'doc lenth is {len(data)}')
    return data

def summary_news():
    map_prompt_temp = """总结这段新闻的内容在50字以内:{text}, 总结:"""
    ch_prompt = PromptTemplate(template=map_prompt_temp, input_variables=['text'])
    chain = load_summarize_chain(llm, chain_type='map_reduce', map_prompt=ch_prompt, combine_prompt=ch_prompt)
# summary = chain_ch.run(doc)
    summary = chain.invoke({"input_documents": doc})['output_text']
    print(summary)  # 这里展示的是中文的总结

2.6 输出解析

很多时候,我们需要的不是一串文字,而是结构化的数据。Langchain中的输出解析器能让模型按照我们预设的格式,比如生成一个JSON对象或一个剧本格式。下面是利用Pydantic定义剧本结构,并让模型根据一段文章生成剧本的示例。

from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

# 注意:这里的BaseModel的类的description 不能用中文,因为到template的prompt的时候,会乱码,导致模型不懂描述的是什么
class Line(BaseModel):
# character:str = Field(description=u"说这句台词的角色名字",)
    character: str = Field(description=u"The name of the character who said this line",)
# content:str = Field(description=u"台词的具体内容,其中不再包含角色的名字")
    content: str = Field(description=u"The specific content of the line, which no longer contains the character's name")

class JuBen(BaseModel):
# script: List[Line] = Field(description=u"一段的台词剧本")
    script: List[Line] = Field(description=u"A talk script")

def parse_process():
    temp = """我将给你一段文章,请按照要求把这段文章改写成一个电视剧的剧本。

                    文章:"{docs}"
                    要求:"{request}"
                    {output_instructions}

            """
    parser = PydanticOutputParser(pydantic_object=JuBen)

    prompt = PromptTemplate(template=temp,
                               input_variables=['docs', 'request'],
                               partial_variables={"output_instructions": parser.get_format_instructions()},
# pattern = re.compile('n')
)
    jb_content = prompt.format_prompt(docs=docs, request="风格大胆悲情,剧本对话角色不少于三个人,以他们的自我介绍为开头")

# msg = [HumanMessage(content=jb_content)]
# rs = llm.predict_messages(msg)
    rs = llm(jb_content.to_string())
    jb = parser.parse(rs)

# chain = jb_prompt | llm | parser
# xiangsheng = chain.invoke({
#     "docs" : docs,
#     "request" : "风格大胆悲情,剧本对话角色不少于三个人,以他们的自我介绍为开头"
# })
# print(jb)
    return jb


免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策