Appearance
缓存
在实际开发 AI 应用的过程中,我们经常会遇到重复输入的情况:
- 同一用户多次询问相同的问题
- 刷新页面或误触按钮触发了相同请求
- 不同用户提出了内容高度相似的问题
如果每次都让大模型重新生成响应,不仅效率低下,还会带来不必要的计算成本。为了解决这个问题,我们可以引入“缓存机制”。
启用缓存的好处
- 提升响应速度:重复问题无需重新调用模型,直接命中缓存结果
- 降低调用成本:尤其对于调用远程 LLM(如 GPT-4),可节省大量费用
- 避免冗余请求:减少系统负担,提升整体并发性能
要启用缓存,非常简单,只需要在模型实例中一行代码就可以启用:
js
import { Ollama } from "@langchain/ollama";
const model = new Ollama({
model: "llama3",
cache: true, // 启用缓存机制
});课堂练习
快速上手案例
解决缓存默认使用的哈希算法不够安全的问题
我们在开启缓存的时候,官方会给出提示。未来会更新成更安全的算法。
- 流式输出下想要实现缓存
js
import { Ollama } from "@langchain/ollama";
import { SecureCache } from "./utils.js";
// 创建安全缓存实例
const secureCache = new SecureCache();
// 配置模型并启用安全缓存
const model = new Ollama({
model: "llama3",
cache: secureCache, // 使用自定义的安全缓存
stream: true,
});
async function callWithStreamAndCache(inputText) {
const start = Date.now();
const stream = await model.stream(inputText);
let fullResponse = "";
for await (const chunk of stream) {
process.stdout.write(chunk); // 实时打印
fullResponse += chunk; // 累积结果,用于缓存比较
}
const end = Date.now();
console.log(`\n⏱️ 本次耗时:${end - start}ms`);
return fullResponse;
}
// 第一次调用(无缓存)
console.log("\n🎯 第一次调用(无缓存):");
await callWithStreamAndCache("请用中文介绍一下AI的影响");
// 第二次调用(应命中缓存)
console.log("\n\n🎯 第二次调用(命中缓存):");
await callWithStreamAndCache("请用中文介绍一下AI的影响");🤔 为什么流式模式下没有命中缓存?
这是因为 LangChain 的默认缓存机制目前只对同步调用接口(如
.invoke()、.predict())生效。这些方法会在调用前先检查是否存在缓存结果,如果有,就直接返回;否则才会请求模型。而
.stream()方法返回的是一个异步迭代器(AsyncIterable),数据是一块一块地实时生成的。当前在 LangChain.js 的实现中,大多数模型(包括 Ollama)对.stream()并未内建完整的缓存处理逻辑。
课堂练习
综合案例:实现如下特性:
- 流式输出
- 支持缓存
- 内容高度相似的问题也采用缓存的方式
js
import { Ollama, OllamaEmbeddings } from "@langchain/ollama";
import cosineSimilarity from "cosine-similarity";
// 负责回答问题的模型
const model = new Ollama({
model: "qwen3.5:4b",
stream: true,
});
// 负责将文本装换为向量的模型
const embedder = new OllamaEmbeddings({
model: "nomic-embed-text",
});
const cache = []; // 用于做缓存的,存储的是一个一个对象
/**
* {
input:inputText, // 用户原始的提示词
embedding: queryEmbedding, // 提示词对应的向量
response: 'xxx', // 模型的回答
}
*/
async function callLLM(inputText, threshold = 0.7) {
// 1. 首先需要将用户的问题转换为向量
const queryEmbedding = await embedder.embedQuery(inputText);
// 2. 做一个余弦相似度比较
// 当前用户问题的向量和缓存里面的每一项做比较
for (const item of cache) {
const score = cosineSimilarity(queryEmbedding, item.embedding);
if (score >= threshold) {
// 说明命中缓存了,直接走缓存的答案
process.stdout.write(item.response); // 实时打印
return; // 直接返回,不继续往下走了
}
}
// 3. 如果代码来到这,没命中缓存
let fullResponse = "";
const stream = await model.stream(inputText);
for await (const chunk of stream) {
process.stdout.write(chunk); // 实时打印
fullResponse += chunk; // 累积结果,用于缓存比较
}
// 4. 存储到缓存里面
cache.push({
input: inputText, // 用户原始的提示词
embedding: queryEmbedding, // 提示词对应的向量
response: fullResponse, // 模型的回答
});
}