# 深入理解异步环境中的回调函数:从入门到定制
回调函数是异步编程中非常重要的概念,它可以在任务执行的不同阶段处理事件或者执行特定操作。在异步环境下,尤其是当涉及像大型语言模型(LLM)或复杂链式调用时,正确地使用回调函数变得至关重要。
本文将深入探讨如何在异步环境中正确实现回调函数,避免常见陷阱,并提供清晰的代码示例来帮助你理解和应用。
---
## 1. 引言
### 什么是回调函数?
回调函数是一个传递给另一个函数的函数,它会在某个事件发生或任务完成时被调用。在异步编程中,回调函数通常用于处理异步任务的结果或监控任务的状态。
### 为什么需要异步回调?
在异步环境中,如果使用同步回调函数,可能会导致主线程阻塞或资源竞争问题,从而影响程序性能。因此,异步回调函数更适合处理高并发、非阻塞的任务。
---
## 2. 实现自定义回调处理器
在处理开源的 `langchain` 库时,你可以通过自定义回调处理器来跟踪大型语言模型的执行过程。这包括任务的启动、生成新内容和任务的结束。
### 注意事项
1. **使用异步回调处理器**: 如果使用同步回调处理器(`syncCallbackHandler`),它会在后台用 `run_in_executor` 调用,这可能带来线程安全问题。
2. **在 Python <= 3.10 的情况下**: 当你从一个 `RunnableLambda` 或 `RunnableGenerator` 调用其他可运行对象时,需手动传递配置或回调,否则子任务中的回调将不会被正确传播。
以下是一个完整的示例:
---
## 3. 代码示例:自定义同步和异步回调处理器
```python
import asyncio
from typing import Any, Dict, List
from langchain_anthropic import ChatAnthropic
from langchain_core.callbacks import AsyncCallbackHandler, BaseCallbackHandler
from langchain_core.messages import HumanMessage
from langchain_core.outputs import LLMResult
# 自定义同步回调处理器(不推荐用于密集的异步操作)
class MyCustomSyncHandler(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs) -> None:
print(f"同步回调被调用:生成的新token: {token}")
# 自定义异步回调处理器,用于高效处理异步任务
class MyCustomAsyncHandler(AsyncCallbackHandler):
"""异步回调处理器,用于处理 LangChain 的回调事件。"""
async def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> None:
"""在任务启动时被调用。"""
print("任务启动:等待处理...")
await asyncio.sleep(0.3) # 模拟异步处理延迟
print(f"模型开始运行:{serialized['name']}")
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
"""在任务结束时被调用。"""
print("任务结束:等待处理...")
await asyncio.sleep(0.3) # 模拟异步处理延迟
print(f"模型运行结束,生成的结果: {response.generations[0][0].text}")
# 使用API代理服务提高访问稳定性
chat = ChatAnthropic(
model="claude-3-example",
max_tokens=25,
streaming=True,
callbacks=[MyCustomSyncHandler(), MyCustomAsyncHandler()] # 注册自定义回调
)
# 异步调用任务
async def main():
response = await chat.agenerate([[HumanMessage(content="讲个笑话")]])
print(f"最终生成的响应:{response.generations[0][0].text}")
# 运行主要函数
if __name__ == "__main__":
asyncio.run(main())
4. 常见问题和解决方案
问题 1:为什么同步回调会有潜在问题?
原因:在异步任务中,同步回调会包裹在 run_in_executor 中运行。如果回调函数内部有线程安全问题,可能会导致不可预测的行为。
解决方案:尽量使用异步回调(AsyncCallbackHandler),避免同步回调的潜在风险。
问题 2:在较旧版本的 Python(<= 3.10)中,为什么回调无法传播?
原因:在 RunnableLambda 或 RunnableGenerator 中,回调不会自动传递到被调用的子任务。
解决方案:手动将 config 和 callbacks 参数传递到子任务。
5. 总结和进一步学习资源
通过本文,你可以更深入地理解异步环境中回调函数的重要性,并掌握如何编写自定义的同步和异步回调处理器。以下是一些进一步学习的资源:
6. 参考资料
- LangChain 文档:ChatAnthropic
- Python 官方 asyncio 文档:异步编程指南
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!