**深入理解异步环境中的回调函数:从入门到定制**

159 阅读4分钟
# 深入理解异步环境中的回调函数:从入门到定制

回调函数是异步编程中非常重要的概念,它可以在任务执行的不同阶段处理事件或者执行特定操作。在异步环境下,尤其是当涉及像大型语言模型(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)中,为什么回调无法传播?

原因:在 RunnableLambdaRunnableGenerator 中,回调不会自动传递到被调用的子任务。

解决方案:手动将 configcallbacks 参数传递到子任务。


5. 总结和进一步学习资源

通过本文,你可以更深入地理解异步环境中回调函数的重要性,并掌握如何编写自定义的同步和异步回调处理器。以下是一些进一步学习的资源:


6. 参考资料


如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!