使用langchain进行异步编程
基于LLM的应用程序通常涉及大量的I/O绑定操作,例如向语言模型、数据库或其他服务进行API调用。异步编程(或异步编程)是一种范式,允许程序在不阻塞其他任务执行的情况下同时执行多个任务,从而提高效率和响应性,特别是在I/O绑定操作中。
在阅读本指南之前,您应该熟悉Python中的异步编程。如果不熟悉,请在网上找到适当的资源来学习如何在Python中进行异步编程。 本指南特别关注在异步上下文中使用LangChain所需了解的内容,假设您已经熟悉异步编程。
Langchain 异步 API
许多LangChain API被设计为异步的,允许您构建高效且响应迅速的应用程序。
通常,任何可能执行I/O操作的方法(例如,进行API调用、读取文件)都会有一个异步的对应方法。
在LangChain中,异步实现与同步实现位于相同的类中,异步方法带有“a”前缀。例如,同步的invoke
方法有一个异步对应方法,称为ainvoke
。
LangChain的许多组件实现了Runnable接口,其中包括对异步执行的支持。这意味着你可以使用Python中的await
关键字异步运行Runnables。
await some_runnable.ainvoke(some_input)
其他组件如嵌入模型和向量存储,虽然未实现可运行接口,但通常仍遵循相同的规则,并在同一类中包含带有“a”前缀的异步版本方法。
例如,
await some_vectorstore.aadd_documents(documents)
使用LangChain表达式语言(LCEL)创建的Runnables也可以异步运行,因为它们实现了完整的Runnable接口。
欲了解更多信息,请查阅您正在使用的特定组件的API参考。
委托给同步方法
最受欢迎的LangChain集成实现了其API的异步支持。例如,许多ChatModel实现的ainvoke
方法使用httpx.AsyncClient
向模型提供者的API发出异步HTTP请求。
当异步实现不可用时,LangChain 会尝试提供一个默认实现,即使这会产生轻微的开销。
默认情况下,LangChain 会将未实现的异步方法的执行委托给同步对应方法。LangChain 几乎总是假设同步方法应被视为阻塞操作,并且应在单独的线程中运行。
这是通过使用 asyncio.loop.run_in_executor 功能实现的,该功能由 asyncio
库提供。LangChain 使用 asyncio
库提供的默认执行器,该执行器会懒初始化一个线程池执行器,并使用默认数量的线程,这些线程在给定的事件循环中重复使用。虽然这种策略由于线程之间的上下文切换而带来轻微的开销,但它保证了每个异步方法都有一个开箱即用的默认实现。
性能
LangChain中的异步代码通常应该能够以最小的开销相对较好地执行,并且在大多数应用程序中不太可能成为瓶颈。
两个主要的开销来源是:
- 当委托给同步方法时,线程之间上下文切换的成本。这可以通过提供本机异步实现来解决。
- 在LCEL中,任何作为链的一部分出现的“廉价函数”将被调度为事件循环上的任务(如果它们是异步的)或在单独的线程中运行(如果它们是同步的),而不是仅仅内联运行。
您应该预期的这些延迟开销在几十微秒到几毫秒之间。
性能问题的一个更常见来源是用户在异步上下文中意外调用同步代码(例如,调用invoke
而不是ainvoke
)而阻塞了事件循环。
兼容性
LangChain 仅兼容 asyncio
库,该库作为 Python 标准库的一部分分发。它不适用于其他异步库,如 trio
或 curio
。
在 Python 3.9 和 3.10 中,asyncio 的任务不接受 context
参数。由于这一限制,LangChain 在某些场景下无法自动将 RunnableConfig
沿调用链传播。
如果您在使用Python 3.9或3.10时遇到流媒体、回调或异步代码中的跟踪问题,这很可能是一个原因。
请阅读Propagation RunnableConfig以了解更多详细信息,了解如何手动将RunnableConfig
沿调用链传播(或升级到Python 3.11,此问题不再存在)。
如何在ipython和jupyter notebooks中使用
自 IPython 7.0 起,IPython 支持异步 REPL。这意味着你可以在 IPython REPL 和 Jupyter Notebooks 中使用 await
关键字,而无需任何额外设置。更多信息,请参阅 IPython 博客文章。