超时与取消:如何利用 AbortSignal 实现请求中断?
在现代 Web 应用和后端服务中,能够控制和取消长时间运行的操作是一个重要的功能。LangChain V3 通过集成标准的 AbortSignal 机制,为开发者提供了强大的超时和取消能力。本章将深入探讨如何在 LangChain V3 中使用 AbortSignal 实现请求中断。
AbortSignal 简介
AbortSignal 是 Web 标准的一部分,用于与可取消的异步操作通信。它允许开发者在操作完成之前中止操作,这对于处理超时、用户取消操作或资源管理非常有用。
typescript
// 创建一个 AbortController
const controller = new AbortController();
const signal = controller.signal;
// 在某个异步操作中使用 signal
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求已被取消');
}
});
// 在任何时候取消操作
controller.abort();在 LangChain V3 中集成 AbortSignal
LangChain V3 通过 将 AbortSignal 集成到执行链中:
typescript
const controller = new AbortController();
const config: RunnableConfig = {
signal: controller.signal
};
// 执行可能需要很长时间的操作
const result = await chain.invoke(input, config);
// 在另一个地方取消操作
controller.abort();实现超时控制
通过 AbortSignal,我们可以轻松实现超时控制:
typescript
function withTimeout(runnable, timeoutMs) {
return new RunnableProxy(runnable, {
onInvoke: async (input, config = {}) => {
// 创建带有超时的 AbortController
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeoutMs);
try {
// 将 signal 添加到配置中
const result = await runnable.invoke(input, {
...config,
signal: controller.signal
});
return result;
} finally {
clearTimeout(timeoutId);
}
}
});
}
// 使用示例
const timeoutChain = withTimeout(chain, 5000); // 5秒超时
try {
const result = await timeoutChain.invoke(input);
} catch (error) {
if (error.name === 'AbortError') {
console.log('操作超时');
}
}用户取消操作
在用户界面中,用户可能希望取消正在进行的操作:
typescript
class ChatApplication {
private abortController: AbortController | null = null;
async sendMessage(message) {
// 取消之前的请求(如果有的话)
if (this.abortController) {
this.abortController.abort();
}
// 创建新的控制器
this.abortController = new AbortController();
try {
const config: RunnableConfig = {
signal: this.abortController.signal
};
const response = await chatChain.invoke(
{ message },
config
);
return response;
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被用户取消');
return null;
}
throw error;
} finally {
this.abortController = null;
}
}
cancelRequest() {
if (this.abortController) {
this.abortController.abort();
}
}
}与现有超时机制的集成
LangChain V3 既支持通过 的属性设置超时,也支持通过 AbortSignal 实现更灵活的控制:
typescript
// 方法1: 使用 timeout 属性
const config1: RunnableConfig = {
timeout: 5000 // 5秒超时
};
// 方法2: 使用 AbortSignal
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const config2: RunnableConfig = {
signal: controller.signal
};
// 方法3: 结合使用
const controller = new AbortController();
const config3: RunnableConfig = {
timeout: 5000,
signal: controller.signal
};在底层 HTTP 客户端中的应用
LangChain V3 中的 LLM 集成通常会使用底层的 HTTP 客户端,这些客户端也支持 AbortSignal:
typescript
class OpenAIChatModel extends BaseLanguageModel {
async _call(prompt, options) {
const { signal } = options;
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: this.modelName,
messages: [{ role: 'user', content: prompt }]
}),
signal // 将 signal 传递给 fetch
});
const data = await response.json();
return data.choices[0].message.content;
}
}批量操作中的取消
在批量操作中,AbortSignal 同样适用:
typescript
const controller = new AbortController();
const config: RunnableConfig = {
signal: controller.signal
};
// 批量处理
const results = await chain.batch(inputs, config);
// 可以在任何时候取消整个批量操作
controller.abort();错误处理和清理
使用 AbortSignal 时,需要注意适当的错误处理和资源清理:
typescript
class CancellableRunnable extends Runnable {
async invoke(input, config = {}) {
const { signal } = config;
// 检查是否已经取消
if (signal?.aborted) {
throw new Error('操作已被取消');
}
// 监听取消事件
return new Promise((resolve, reject) => {
const onAbort = () => {
cleanup(); // 清理资源
reject(new Error('操作已被取消'));
};
signal?.addEventListener('abort', onAbort);
// 执行实际操作
this.performOperation(input)
.then(result => {
signal?.removeEventListener('abort', onAbort);
resolve(result);
})
.catch(error => {
signal?.removeEventListener('abort', onAbort);
reject(error);
});
});
}
private performOperation(input) {
// 实际的执行逻辑
}
private cleanup() {
// 清理资源,如关闭连接、删除临时文件等
}
}实际应用示例
下面是一个完整的实际应用示例,展示了如何在聊天应用中使用 AbortSignal:
typescript
class ChatBot {
private currentRequestController: AbortController | null = null;
async processUserMessage(userMessage: string) {
// 取消之前的请求
this.cancelCurrentRequest();
// 创建新的控制器
this.currentRequestController = new AbortController();
try {
// 构建聊天历史
const chatHistory = this.getChatHistory();
// 创建配置
const config: RunnableConfig = {
signal: this.currentRequestController.signal,
metadata: {
userId: this.currentUserId,
timestamp: Date.now()
}
};
// 执行聊天链
const response = await this.chatChain.invoke({
history: chatHistory,
input: userMessage
}, config);
return response;
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被取消');
return "请求已被取消";
}
throw error;
} finally {
this.currentRequestController = null;
}
}
cancelCurrentRequest() {
if (this.currentRequestController) {
this.currentRequestController.abort();
this.currentRequestController = null;
}
}
}总结
通过集成标准的 AbortSignal 机制,LangChain V3 为开发者提供了强大而灵活的超时和取消功能。这种设计不仅符合现代 Web 标准,还为构建响应式和用户友好的 LLM 应用提供了坚实的基础。
关键要点包括:
AbortSignal是 Web 标准,LangChain V3 通过 集成它- 可以实现超时控制、用户取消操作等场景
- 与底层 HTTP 客户端无缝集成
- 支持单次操作和批量操作
- 需要适当的错误处理和资源清理
在下一章中,我们将深入探讨 LCEL 的本质,了解函数式管道与范畴论的实践。