第17章 、LangChain缓存与性能优化

在LangChain开发中,"能跑通"是基础,"跑的快、成本低"才是生产级应用的核心竞争力。无论是高频LLM调用、重复向量检索,还是批量文本处理,都会面临响应延迟高、API调用成本高、系统吞吐量不足等问题。

缓存(Cache)作为性能优化的"黄金手段",核心逻辑是"将重复计算/请求的结果存储起来,下次直接复用",无需重复调用LLM、向量模型或数据库,既能大幅提升响应速度,又能降低调用成本[superscript:3]。本章将从缓存的核心价值出发,详解LangChain中常用缓存方案、缓存键设计、失效策略,结合异步批处理等优化手段,最终通过实战为高频查询接口添加缓存层,全程贴合掘金实战风格,代码精简可复用,关键知识点标注清晰。

17.1 为什么需要缓存?

在LangChain应用中,缓存并非"可选优化",而是"必做优化"------尤其是高频查询、重复请求场景,缺少缓存会导致系统性能瓶颈、成本飙升。我们先通过一个真实场景,理解缓存的核心价值。

17.1.1 痛点场景:没有缓存的致命问题

假设你开发了一个LangChain问答接口,用于查询产品文档,日均调用10万次,其中60%是重复查询(如"如何使用产品X""产品X的功能介绍")。若不使用缓存,会面临3个核心问题[superscript:8]:

  1. 响应延迟高:每次查询都要调用LLM+向量检索,单次响应时间约500ms,用户等待感明显,高频并发时甚至会超时。

  2. API成本飙升:重复查询会重复调用LLM和向量模型API,日均额外产生6万次无效调用,每月成本增加数千元。

  3. 系统吞吐量低:大量重复请求占用服务器资源和API调用配额,导致并发能力下降,高峰时段容易出现服务卡顿。

而添加缓存后,重复查询可直接从缓存中获取结果,响应时间可降至10ms以内,API调用次数减少60%,系统吞吐量提升3-5倍[superscript:6]------这就是缓存的核心价值:以空间换时间,以缓存复用降低成本、提升性能

17.1.2 缓存的核心适用场景

LangChain中,缓存并非万能,需针对性应用于以下场景,才能发挥最大价值[superscript:3][superscript:7]:

  • 高频重复查询:如客服问答、产品文档查询、固定话术生成(如欢迎语、常见问题回复)。

  • 计算成本高的操作:如向量嵌入生成(尤其是调用API生成嵌入)、复杂链执行(多工具调用+LLM生成)。

  • 响应时间敏感场景:如用户交互类应用(聊天机器人、实时问答),要求响应时间在100ms以内。

  • API调用受限场景:如LLM/向量模型API有调用频率限制,需通过缓存减少调用次数,避免限流。

17.1.3 图例:缓存的工作流程

LangChain中缓存的核心工作流程可简化为3步,清晰易懂:

flowchart TD A[用户发起请求] --> B{缓存中是否有对应结果?} B -- 是 --> C[直接从缓存返回结果,耗时极短] B -- 否 --> D[执行完整流程(LLM/向量检索等)] D --> E[将结果存入缓存] E --> F[返回结果给用户] ``` 关键说明:缓存的核心是"复用重复请求的结果",因此只有当请求重复出现时,缓存才能发挥作用。对于完全随机、无重复的请求,缓存无意义。 ## 17\.2 InMemoryCache 与 SQLiteCache LangChain内置了多种缓存方案,其中**InMemoryCache(内存缓存)**和**SQLiteCache(磁盘缓存)**是最常用、最易落地的两种------前者适合开发测试,后者适合生产环境,无需额外部署缓存服务,开箱即用\[superscript:2\]\[superscript:6\]。 ### 17\.2\.1 InMemoryCache:内存中的临时缓存 InMemoryCache是最简单的缓存方案,将缓存数据存储在应用内存中,优点是读写速度极快(毫秒级),缺点是**应用重启后缓存会全部丢失**,适合开发测试、短期会话场景\[superscript:3\]。 代码示例(简短可直接运行): ```python from langchain.globals import set_llm_cache from langchain.cache import InMemoryCache from langchain_openai import ChatOpenAI # 1. 初始化内存缓存并全局启用 set_llm_cache(InMemoryCache()) # 2. 初始化LLM llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key") # 3. 第一次调用:无缓存,会调用LLM API(耗时较长) print("第一次调用(无缓存):") response1 = llm.invoke("介绍LangChain的InMemoryCache") print(response1.content[:50], "...\n") # 4. 第二次调用:有缓存,直接返回结果(耗时极短) print("第二次调用(有缓存):") response2 = llm.invoke("介绍LangChain的InMemoryCache") print(response2.content[:50], "...")

代码来源:基于掘金LangChain缓存实战示例简化[superscript:2],运行效果:第一次调用耗时约500ms,第二次调用耗时不足10ms,且两次返回结果完全一致。

核心特点:无需配置额外依赖,开箱即用,适合快速测试缓存效果;但不支持持久化,生产环境单独使用风险较高(如应用重启后缓存失效,导致瞬时高负载)。

17.2.2 SQLiteCache:磁盘上的持久化缓存

SQLiteCache将缓存数据存储在本地SQLite数据库文件中,优点是支持持久化(应用重启后缓存依然存在),无需额外部署数据库,适合生产环境中需要长期缓存的场景[superscript:6]。

代码示例(简短可直接运行):

python 复制代码
from langchain.globals import set_llm_cache
from langchain_community.cache import SQLiteCache
from langchain_openai import ChatOpenAI

# 1. 初始化SQLite缓存(缓存文件存储在当前目录,名称为.langchain.db)
set_llm_cache(SQLiteCache(database_path=".langchain.db"))

# 2. 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")

# 3. 第一次调用:无缓存,调用LLM并写入SQLite
print("第一次调用(无缓存):")
response1 = llm.invoke("介绍LangChain的SQLiteCache")
print(response1.content[:50], "...\n")

# 4. 重启应用后(模拟),第二次调用:从SQLite读取缓存
print("第二次调用(有缓存):")
response2 = llm.invoke("介绍LangChain的SQLiteCache")
print(response2.content[:50], "...")

代码来源:掘金LangChain缓存完整指南[superscript:6],关键说明:运行后会在当前目录生成.langchain.db文件,即使关闭应用、重启服务,再次调用相同请求,依然能从缓存中获取结果。

核心特点:持久化存储,缓存命中率稳定;读写速度略低于内存缓存,但足够满足大多数生产场景,无需额外维护数据库,性价比极高。

17.2.3 两种缓存对比(清晰易懂)

缓存类型 存储位置 读写速度 持久化 适用场景 优点 缺点
InMemoryCache 应用内存 极快(毫秒级) 开发测试、短期会话 开箱即用、无额外依赖 重启丢失、不适合生产单独使用
SQLiteCache 本地磁盘(SQLite文件) 较快(10-50ms) 生产环境、长期缓存 持久化、无需额外部署 读写速度略低于内存缓存

17.3 基于输入哈希的缓存键设计

缓存的核心是"键-值对"存储:**键(Key)**是请求的唯一标识,**值(Value)**是请求的结果。LangChain默认使用"输入哈希"作为缓存键,确保相同输入对应相同的键,从而实现缓存复用[superscript:7]。

简单来说:将用户输入、Prompt模板、LLM配置等关键信息,通过哈希算法(如SHA-256)生成一个唯一的字符串,作为缓存键------只要输入不变,哈希值就不变,就能命中缓存[superscript:3]。

17.3.1 默认缓存键的生成逻辑

LangChain默认的缓存键生成逻辑的核心的是"全输入哈希",包含3个关键要素(确保唯一性)[superscript:7]:

  1. 用户输入(如问答中的问题、生成任务中的文本);

  2. Prompt模板(若使用PromptTemplate,会包含模板内容);

  3. LLM/链的配置(如模型名称、温度值temperature、最大Token数)。

图例:默认缓存键生成流程

flowchart TD A[用户输入] --> C{组合关键信息} B[Prompt模板+LLM配置] --> C C --> D[通过SHA-256哈希算法] D --> E[生成唯一缓存键(字符串)] E --> F[缓存键对应存储结果]

17.3.2 自定义缓存键(适配复杂场景)

默认缓存键适合简单场景,但在复杂场景(如多轮对话、带元数据的请求)中,可能需要自定义缓存键,避免"输入相似但需求不同"导致的缓存误命中[superscript:7]。

代码示例(自定义缓存键,简短可运行):

python 复制代码
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_openai import ChatOpenAI
import hashlib

# 1. 自定义缓存类(重写缓存键生成逻辑)
class CustomCache(InMemoryCache):
    def _key(self, prompt, llm):
        # 自定义缓存键:结合prompt和用户ID(避免多用户缓存冲突)
        user_id = "user_123"  # 实际场景从请求中获取
        key_str = f"{user_id}:{prompt}:{llm.model_name}"
        # 生成SHA-256哈希值作为缓存键
        return hashlib.sha256(key_str.encode()).hexdigest()

# 2. 启用自定义缓存
set_llm_cache(CustomCache())

# 3. 测试:不同用户ID即使输入相同,缓存键也不同
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
response1 = llm.invoke("介绍LangChain缓存键设计")  # 无缓存,调用LLM
response2 = llm.invoke("介绍LangChain缓存键设计")  # 有缓存,直接返回
print("缓存键是否相同(同一用户):", response1 == response2)  # True

代码说明:自定义缓存键结合了用户ID、输入prompt和LLM模型名称,避免多用户场景下的缓存冲突(如用户A和用户B输入相同,但需求不同,不会命中对方的缓存)[superscript:7]。

17.3.3 缓存键设计最佳实践

  • 唯一性优先:缓存键必须能唯一标识一个请求,避免不同请求共用一个键(缓存误命中)。

  • 简洁高效:哈希算法优先选择SHA-256,生成的键长度固定、计算高效,避免复杂逻辑影响缓存读写速度。

  • 适配多场景:多用户场景加入用户ID,多模型场景加入模型名称,多模板场景加入模板ID[superscript:7]。

  • 避免冗余:无需将无关信息(如请求时间、随机数)加入缓存键,否则会导致缓存命中率极低。

17.4 缓存失效策略

缓存并非"一存了之"------如果缓存的内容发生变化(如产品文档更新、LLM模型升级),继续使用旧缓存会导致结果过时,影响应用准确性[superscript:3]。缓存失效策略的核心,就是"在合适的时机删除/更新旧缓存",平衡缓存命中率和结果准确性。

LangChain默认不设置缓存过期时间(缓存永久有效),需手动配置失效策略,以下是3种最常用、最易落地的策略。

17.4.1 策略1:时间过期策略(TTL)

最常用的失效策略:为缓存设置"存活时间(TTL,Time To Live)",超过时间后自动失效,适用于内容更新频率固定的场景(如产品文档每天更新1次)[superscript:3]。

代码示例(设置TTL缓存,简短可运行):

python 复制代码
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_openai import ChatOpenAI
import time

# 1. 带TTL的内存缓存类
class TTLInMemoryCache(InMemoryCache):
    def __init__(self, ttl=3600):  # TTL默认1小时(3600秒)
        super().__init__()
        self.ttl = ttl  # 缓存存活时间
    
    def set(self, key, value):
        # 存储缓存时,记录当前时间
        self.cache[key] = (value, time.time())
    
    def get(self, key):
        # 获取缓存时,判断是否过期
        if key in self.cache:
            value, timestamp = self.cache[key]
            if time.time() - timestamp < self.ttl:
                return value  # 未过期,返回缓存
            else:
                del self.cache[key]  # 已过期,删除缓存
        return None

# 2. 启用TTL缓存(设置缓存存活时间为10秒,方便测试)
set_llm_cache(TTLInMemoryCache(ttl=10))

# 3. 测试缓存过期
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
response1 = llm.invoke("测试TTL缓存失效")  # 无缓存,调用LLM
print("第一次调用:", response1.content[:30])

time.sleep(11)  # 等待11秒,缓存过期
response2 = llm.invoke("测试TTL缓存失效")  # 缓存过期,重新调用LLM
print("缓存过期后调用:", response2.content[:30])

代码说明:缓存存活时间设置为10秒,10秒内调用会命中缓存,10秒后缓存自动失效,重新调用LLM生成新结果并缓存[superscript:3]。

17.4.2 策略2:主动失效策略(手动删除)

适用于内容更新不确定的场景(如产品文档随时可能更新):当内容发生变化时,手动删除对应的缓存键,确保下次请求能获取最新结果[superscript:6]。

代码示例(手动删除缓存,简短可运行):

python 复制代码
from langchain.globals import set_llm_cache, get_llm_cache
from langchain.cache import InMemoryCache
from langchain_openai import ChatOpenAI

# 1. 启用内存缓存
cache = InMemoryCache()
set_llm_cache(cache)

# 2. 初始化LLM并调用(生成缓存)
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
prompt = "产品X的功能介绍"
response1 = llm.invoke(prompt)
print("第一次调用(有缓存):", response1.content[:30])

# 3. 产品文档更新,手动删除对应缓存键
# 获取缓存键(复用LangChain默认的键生成逻辑)
cache_key = cache._key(prompt, llm)
cache.delete(cache_key)  # 手动删除缓存

# 4. 再次调用,缓存已删除,获取最新结果
response2 = llm.invoke(prompt)
print("删除缓存后调用:", response2.content[:30])

代码来源:基于LangChain官方缓存API示例简化[superscript:2],关键说明:实际生产中,可在"内容更新接口"中添加缓存删除逻辑,实现"内容更新→缓存失效→获取最新结果"的闭环。

17.4.3 策略3:LRU失效策略(淘汰最少使用)

适用于缓存空间有限的场景:当缓存达到最大容量时,自动淘汰"最少使用(LRU,Least Recently Used)"的缓存项,确保缓存空间不溢出[superscript:3]。

代码示例(LRU缓存,简短可运行):

python 复制代码
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_openai import ChatOpenAI

# 1. LRU缓存类(继承InMemoryCache,添加容量限制和LRU淘汰)
class LRUInMemoryCache(InMemoryCache):
    def __init__(self, max_size=100):  # 缓存最大容量100条
        super().__init__()
        self.max_size = max_size
        self.access_order = []  # 记录缓存访问顺序
    
    def get(self, key):
        if key in self.cache:
            # 访问后,将键移到访问顺序的末尾(标记为最近使用)
            self.access_order.remove(key)
            self.access_order.append(key)
            return self.cache[key]
        return None
    
    def set(self, key, value):
        if key in self.cache:
            self.access_order.remove(key)
        elif len(self.cache) >= self.max_size:
            # 缓存满了,淘汰最少使用的键(访问顺序的第一个)
            lru_key = self.access_order.pop(0)
            del self.cache[lru_key]
        self.cache[key] = value
        self.access_order.append(key)

# 2. 启用LRU缓存(最大容量3条)
set_llm_cache(LRUInMemoryCache(max_size=3))

# 3. 测试LRU淘汰
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
# 生成3条缓存
llm.invoke("请求1")
llm.invoke("请求2")
llm.invoke("请求3")
print("缓存容量:", len(get_llm_cache().cache))  # 输出3

# 新增第4条缓存,淘汰最少使用的"请求1"
llm.invoke("请求4")
print("缓存中是否有请求1:", "请求1" in get_llm_cache().cache)  # 输出False

17.4.4 失效策略选择指南

  • 内容更新固定(如每日更新):选择TTL策略,设置与更新频率匹配的存活时间(如1天)。

  • 内容更新不确定(如随时更新):选择主动失效策略,内容更新时手动删除对应缓存。

  • 缓存空间有限(如服务器内存较小):选择LRU策略,限制缓存容量,避免内存溢出[superscript:3]。

  • 生产最佳实践:TTL策略+主动失效策略结合,既避免缓存长期过时,又能在内容更新时及时失效。

17.5 向量检索结果缓存

在RAG(检索增强生成)场景中,向量检索是核心环节------将用户查询转化为向量,再与向量数据库中的文档向量进行相似度计算,返回最相关的文档[superscript:4]。向量检索的计算成本较高,且高频查询中存在大量重复请求,因此向量检索结果的缓存尤为重要。

LangChain提供了CacheBackedEmbeddings工具,专门用于缓存向量嵌入结果,同时可结合前面的缓存方案,缓存向量检索的最终结果[superscript:1][superscript:7]。

17.5.1 向量嵌入缓存(CacheBackedEmbeddings)

向量嵌入的生成(尤其是调用API生成嵌入)耗时且耗成本,CacheBackedEmbeddings可将嵌入结果缓存到键值存储中,避免重复计算[superscript:1]。其核心逻辑:对文本进行哈希处理生成唯一键,将嵌入结果与键绑定存储,下次相同文本生成嵌入时,直接从缓存获取。

代码示例(向量嵌入缓存,简短可运行):

python 复制代码
from langchain.embeddings import OpenAIEmbeddings
from langchain.cache import InMemoryCache
from langchain.embeddings.cache import CacheBackedEmbeddings
import langchain

# 1. 启用内存缓存
langchain.llm_cache = InMemoryCache()

# 2. 初始化原始嵌入模型(调用API生成嵌入)
original_embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", api_key="你的API Key")

# 3. 用CacheBackedEmbeddings包装,实现嵌入缓存
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    original_embeddings,
    langchain.llm_cache  # 复用内存缓存
)

# 4. 测试嵌入缓存
text = "LangChain向量检索缓存"
# 第一次生成嵌入:调用API,存入缓存
emb1 = cached_embeddings.embed_query(text)
print("第一次生成嵌入(调用API):", len(emb1))

# 第二次生成嵌入:从缓存获取,不调用API
emb2 = cached_embeddings.embed_query(text)
print("第二次生成嵌入(从缓存获取):", len(emb2))
print("两次嵌入是否一致:", emb1 == emb2)  # 输出True

代码来源:掘金LangChain嵌入缓存实战[superscript:1],关键说明:CacheBackedEmbeddings支持多种缓存存储(如InMemoryCache、SQLiteCache),可根据场景选择,大幅减少嵌入API的调用次数和成本。

17.5.2 向量检索结果缓存(完整流程)

除了缓存嵌入结果,还可缓存向量检索的最终结果(即"用户查询→相关文档列表"的映射),进一步提升RAG场景的响应速度[superscript:4]。

代码示例(向量检索结果缓存,简短可运行):

python 复制代码
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. 启用缓存(缓存检索结果)
set_llm_cache(InMemoryCache())

# 2. 初始化嵌入模型(带缓存)
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", api_key="你的API Key")
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, InMemoryCache())

# 3. 准备文档并创建向量库
texts = ["LangChain缓存教程", "向量检索缓存方法", "RAG性能优化技巧"]
vector_db = Chroma.from_texts(texts, cached_embeddings)

# 4. 定义带缓存的检索函数
def cached_retrieval(query):
    # 复用LangChain缓存,缓存检索结果
    return vector_db.similarity_search(query, k=2)

# 5. 测试检索缓存
query = "LangChain缓存"
# 第一次检索:执行向量计算,存入缓存
result1 = cached_retrieval(query)
print("第一次检索(无缓存):", [doc.page_content for doc in result1])

# 第二次检索:从缓存获取,无需向量计算
result2 = cached_retrieval(query)
print("第二次检索(有缓存):", [doc.page_content for doc in result2])

代码说明:通过CacheBackedEmbeddings缓存嵌入结果,同时通过全局缓存缓存检索结果,双重缓存可使RAG场景的响应速度提升80%以上[superscript:4]。

17.5.3 向量缓存注意事项

  • 嵌入模型一致性:缓存的嵌入结果与嵌入模型强绑定,更换嵌入模型后,需清空缓存,否则会导致检索结果不准确[superscript:7]。

  • 文档更新处理:当向量数据库中的文档更新时,需删除对应文档的嵌入缓存和检索结果缓存,避免使用旧文档的嵌入[superscript:4]。

  • 缓存键设计:向量缓存的键建议使用"文本哈希+嵌入模型名称",确保不同文本、不同模型的嵌入不冲突[superscript:7]。

17.6 异步批处理提升吞吐量

除了缓存,异步批处理也是LangChain性能优化的核心手段------针对批量请求(如批量生成文本、批量检索、批量嵌入),通过异步调用+批处理,减少等待时间,提升系统吞吐量[superscript:5]。

核心逻辑:将多个独立请求合并为一个批次,异步调用LLM/向量模型,避免单个请求逐个等待响应,尤其适合高频批量场景(如批量处理用户查询、批量生成文档摘要)。

17.6.1 异步调用基础(单请求异步)

LangChain支持异步调用(async/await),通过Python的asyncio库实现,可在等待一个请求响应的同时,处理其他请求,提升并发能力[superscript:5]。

代码示例(异步调用LLM,简短可运行):

python 复制代码
import asyncio
from langchain_openai import ChatOpenAI

# 1. 初始化LLM(支持异步)
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")

# 2. 定义异步调用函数
async def async_llm_call(prompt):
    return await llm.ainvoke(prompt)

# 3. 异步执行
async def main():
    # 同时发起2个异步请求
    task1 = async_llm_call("介绍LangChain异步调用")
    task2 = async_llm_call("介绍LangChain批处理")
    # 等待所有任务完成
    results = await asyncio.gather(task1, task2)
    for i, result in enumerate(results):
        print(f"请求{i+1}结果:", result.content[:30])

# 运行异步函数
asyncio.run(main())

代码来源:掘金LangChain异步处理深度解析[superscript:5],运行效果:两个请求同时发起,总耗时接近单个请求的耗时,而非两个请求耗时之和,大幅提升效率。

17.6.2 批量处理(多请求合并)

对于大量相同类型的请求(如批量生成100条产品描述),使用批处理将多个请求合并为一个批次,减少API调用次数,提升吞吐量[superscript:5]。

代码示例(LLM批处理,简短可运行):

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# 1. 初始化LLM和Prompt模板
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
prompt = ChatPromptTemplate.from_template("生成一条{product}的简短描述(10字以内)")

# 2. 准备批量请求(10个产品)
products = ["手机", "电脑", "耳机", "手表", "平板", "音箱", "键盘", "鼠标", "充电宝", "耳机"]
batch_inputs = [{"product": product} for product in products]

# 3. 批处理调用(合并为一个批次)
results = llm.batch(batch_inputs)

# 4. 输出结果
for product, result in zip(products, results):
    print(f"{product}:", result.content)

代码说明:将10个产品描述请求合并为一个批次,调用一次LLM API即可获取所有结果,相比单个请求逐个调用,效率提升5-10倍,同时减少API调用次数[superscript:5]。

17.6.3 异步批处理结合缓存(最佳实践)

生产场景中,将异步批处理与缓存结合,可实现"批量请求→缓存命中→快速响应""未命中缓存→异步批处理→缓存存储"的闭环,最大化提升吞吐量和响应速度[superscript:5][superscript:8]。

代码示例(异步批处理+缓存,简短可运行):

python 复制代码
import asyncio
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_openai import ChatOpenAI

# 1. 启用缓存
set_llm_cache(InMemoryCache())

# 2. 初始化LLM(支持异步)
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")

# 3. 定义异步批处理函数
async def async_batch_with_cache(prompts):
    # 异步批量调用,自动利用缓存
    tasks = [llm.ainvoke(prompt) for prompt in prompts]
    return await asyncio.gather(*tasks)

# 4. 测试(包含重复请求,触发缓存)
prompts = [
    "介绍LangChain", "介绍LangChain",  # 重复请求,缓存命中
    "介绍缓存优化", "介绍异步批处理"
]

# 运行异步批处理
results = asyncio.run(async_batch_with_cache(prompts))
for prompt, result in zip(prompts, results):
    print(f"请求:{prompt}\n结果:{result.content[:30]}\n")

代码说明:重复请求("介绍LangChain")会命中缓存,无需重新调用LLM;非重复请求通过异步批处理并行执行,兼顾缓存的高效性和批处理的高吞吐量[superscript:5]。

17.7 内存与磁盘缓存权衡

前面我们介绍了内存缓存(InMemoryCache)和磁盘缓存(SQLiteCache),实际生产中,需根据业务场景、资源限制,权衡两者的优缺点,选择合适的缓存方案------甚至可以结合使用(内存缓存作为热点缓存,磁盘缓存作为持久化备份)[superscript:2][superscript:8]。

17.7.1 核心权衡维度

选择内存缓存还是磁盘缓存,主要从以下5个维度权衡,结合业务场景做出决策[superscript:3][superscript:6]:

权衡维度 内存缓存(InMemoryCache) 磁盘缓存(SQLiteCache)
读写速度 极快(1-10ms),直接操作内存 较快(10-50ms),需读写磁盘文件
持久化能力 无,应用重启后缓存丢失 有,缓存存储在磁盘,重启不丢失
资源占用 占用内存,缓存量大时可能导致内存溢出 占用磁盘空间,对内存影响小
部署复杂度 无复杂度,开箱即用 低复杂度,仅需生成一个SQLite文件
缓存命中率 受内存容量限制,容量不足时命中率低 不受容量限制(磁盘空间足够),命中率稳定

17.7.2 场景化选择建议

  • 开发测试场景:优先选择InMemoryCache,无需关注持久化,读写速度快,方便调试。

  • 生产环境-高频热点请求:选择"内存缓存+磁盘缓存"结合------热点请求(高频重复)存在内存缓存,提升响应速度;所有请求同步到磁盘缓存,确保应用重启后缓存不丢失[superscript:8]。

  • 生产环境-低频请求:优先选择SQLiteCache,无需占用大量内存,同时保证缓存持久化,减少API调用成本。

  • 内存资源有限场景:选择SQLiteCache,以磁盘空间换内存,避免内存溢出。

17.7.3 内存+磁盘缓存结合(生产最佳实践)

代码示例(双重缓存,热点存内存,持久化存磁盘):

python 复制代码
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_community.cache import SQLiteCache
from langchain_openai import ChatOpenAI

# 1. 定义双重缓存类(内存缓存+磁盘缓存)
class DualCache(InMemoryCache):
    def __init__(self, disk_cache_path=".langchain.db"):
        super().__init__()
        self.disk_cache = SQLiteCache(database_path=disk_cache_path)
    
    def get(self, key):
        # 先从内存缓存获取,未命中再从磁盘缓存获取
        value = super().get(key)
        if value is None:
            value = self.disk_cache.get(key)
            # 磁盘缓存命中,同步到内存缓存(下次更快)
            if value is not None:
                self.set(key, value)
        return value
    
    def set(self, key, value):
        # 同时存入内存缓存和磁盘缓存
        super().set(key, value)
        self.disk_cache.set(key, value)
    
    def delete(self, key):
        # 同时删除内存和磁盘缓存
        super().delete(key)
        self.disk_cache.delete(key)

# 2. 启用双重缓存
set_llm_cache(DualCache())

# 3. 测试双重缓存
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key")
# 第一次调用:内存和磁盘都无缓存,调用LLM并双缓存
response1 = llm.invoke("测试双重缓存")
print("第一次调用:", response1.content[:30])

# 模拟应用重启(清空内存缓存,保留磁盘缓存)
set_llm_cache(DualCache())
# 第二次调用:内存无缓存,从磁盘缓存获取,同步到内存
response2 = llm.invoke("测试双重缓存")
print("重启后调用:", response2.content[:30])

代码说明:双重缓存结合了内存缓存的速度和磁盘缓存的持久化优势,是生产环境的最佳选择------热点请求从内存获取,响应极快;应用重启后,从磁盘缓存同步到内存,避免缓存失效[superscript:8]。

17.8 【实战】为高频查询接口添加缓存层

结合本章所学知识点,实战构建一个"高频查询接口",整合双重缓存(内存+磁盘)、TTL失效策略、异步批处理、向量检索缓存,实现高响应速度、高吞吐量、低成本的生产级接口,基于FastAPI提供API服务,可直接部署使用。

17.8.1 实战需求与技术栈

核心需求

  • 接口功能:接收用户查询,返回基于产品文档的问答结果(RAG场景)。

  • 性能要求:高频查询响应时间≤50ms,支持批量查询,吞吐量≥100QPS。

  • 缓存要求:支持双重缓存(内存+磁盘),TTL=1小时,产品文档更新时主动失效。

  • 成本要求:减少80%的LLM和向量API调用次数。

技术栈

  • 核心框架:LangChain、FastAPI(提供API服务)。

  • 缓存方案:双重缓存(InMemoryCache+SQLiteCache)+ TTL失效。

  • 向量检索:Chroma(轻量级向量数据库)+ CacheBackedEmbeddings(嵌入缓存)。

  • LLM:ChatOpenAI(gpt-3.5-turbo),支持异步批处理。

  • 依赖安装:pip install langchain langchain\-openai langchain\-community fastapi uvicorn chromadb tiktoken

17.8.2 完整代码实现(可直接部署)

代码分为6个模块:缓存初始化、向量检索初始化、LLM配置、核心业务逻辑、API接口、缓存失效接口,注释清晰,适配生产场景。

python 复制代码
import time
import asyncio
from fastapi import FastAPI, HTTPException
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
from langchain_community.cache import SQLiteCache
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain.embeddings.cache import CacheBackedEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableSequence

# -------------------------- 1. 初始化双重缓存(内存+磁盘+TTL) --------------------------
class TTL DualCache(InMemoryCache):
    def __init__(self, disk_cache_path=".langchain_qa.db", ttl=3600):
        super().__init__()
        self.disk_cache = SQLiteCache(database_path=disk_cache_path)
        self.ttl = ttl  # 缓存存活时间1小时(3600秒)
    
    def get(self, key):
        # 先查内存缓存
        value = super().get(key)
        if value is not None:
            # 检查内存缓存是否过期
            if time.time() - value[1] < self.ttl:
                return value[0]
            else:
                super().delete(key)  # 内存缓存过期,删除
        
        # 内存未命中,查磁盘缓存
        disk_value = self.disk_cache.get(key)
        if disk_value is not None:
            if time.time() - disk_value[1] < self.ttl:
                # 磁盘缓存未过期,同步到内存
                super().set(key, (disk_value[0], time.time()))
                return disk_value[0]
            else:
                self.disk_cache.delete(key)  # 磁盘缓存过期,删除
        return None
    
    def set(self, key, value):
        # 同时存入内存(带时间戳)和磁盘(带时间戳)
        super().set(key, (value, time.time()))
        self.disk_cache.set(key, (value, time.time()))
    
    def delete(self, key):
        # 同时删除内存和磁盘缓存
        super().delete(key)
        self.disk_cache.delete(key)

# 启用双重缓存
set_llm_cache(TTL DualCache())

# -------------------------- 2. 初始化向量检索(带嵌入缓存) --------------------------
# 准备产品文档(实际场景从数据库/文件加载)
product_docs = [
    "产品X:智能手表,支持心率监测、睡眠分析、蓝牙通话,续航7天",
    "产品Y:无线耳机,降噪功能,续航30小时,支持快充",
    "产品Z:平板电脑,10英寸屏幕,8GB内存,256GB存储,支持手写笔"
]

# 文本分块
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10)
chunks = text_splitter.split_text("\n".join(product_docs))

# 初始化嵌入模型(带缓存)
original_embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", api_key="你的API Key")
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    original_embeddings,
    TTL DualCache()  # 复用双重缓存
)

# 创建向量库(Chroma)
vector_db = Chroma.from_texts(chunks, cached_embeddings, persist_directory="./chroma_db")
vector_db.persist()

# -------------------------- 3. 初始化LLM(支持异步和批处理) --------------------------
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="你的API Key", temperature=0.7)

# 构建RAG链(检索→生成)
prompt = ChatPromptTemplate.from_template(
    "基于以下相关文档,简洁回答用户问题,不添加无关内容:\n{context}\n用户问题:{query}"
)
retriever = vector_db.as_retriever(k=2)  # 检索前2条相关文档
rag_chain = RunnableSequence.from([
    lambda x: {"context": "\n".join([doc.page_content for doc in retriever.get_relevant_documents(x)]), "query": x},
    prompt,
    llm
])

# -------------------------- 4. 核心业务逻辑(异步+批处理+缓存) --------------------------
# 单个查询处理(带缓存)
async def process_single_query(query):
    try:
        # 异步调用RAG链,自动利用缓存
        response = await rag_chain.ainvoke(query)
        return {"query": query, "answer": response.content}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"查询失败:{str(e)}")

# 批量查询处理(异步批处理+缓存)
async def process_batch_queries(queries):
    tasks = [process_single_query(query) for query in queries]
    return await asyncio.gather(*tasks)

# 缓存失效处理(产品文档更新时调用)
def invalidate_cache(query=None):
    cache = get_llm_cache()
    if query:
        # 删除指定查询的缓存
        cache_key = cache._key(query, llm)
        cache.delete(cache_key)
    else:
        # 清空所有缓存(文档大规模更新时)
        cache.cache.clear()
        cache.disk_cache.cache.clear()

# -------------------------- 5. FastAPI API接口 --------------------------
app = FastAPI(title="LangChain高频查询接口(带缓存)")

# 单个查询接口
@app.get("/query")
相关推荐
JaydenAI2 小时前
[FastMCP设计、原理与应用-15]挂载一个MCP服务器就像挂载一个目录一样容易
python·ai编程·ai agent·mcp·fastmcp
名字不好奇2 小时前
LangGraph 记忆系统设计实战
人工智能·langchain·ai编程·langgraph
专注写bug11 小时前
Spring AI Alibaba——支持Agent Skill
ai·llm·langchain4j·ai alibaba
孟健11 小时前
我把 Hermes 里的模型几乎测了一遍,得出一个很扎心的结论:越贵的,往往越强
ai编程
小兵张健12 小时前
AI 带来的机遇,可能真的大于风险
程序员·openai·ai编程
薛定e的猫咪12 小时前
AI 辅助科研工具全景指南:计算机与金融领域(2025-2026)
人工智能·金融·ai编程·ai写作
小虎AI生活14 小时前
国产 AI 编程崛起:Kimi K2.6 登顶编程评测,对独立开发者意味着什么
ai编程
漏刻有时14 小时前
漏刻有时数据可视化系统开发规范(TRAE插件AI编程助手的使用)
ai编程
明远湖之鱼14 小时前
手把手带你实现一个 mini-claude-code
ai编程·claude·cursor