今天和大家一起学习了 LangChain 中的回调函数和异步编程,这里整理了一份轻松易懂的学习笔记,分享一些关键知识点和代码示例,希望对你有所帮助!
什么是回调函数?
回调函数并不陌生。简单来说,它就是把函数 A 作为参数传递给函数 B,并在函数 B 内部调用函数 A。这一机制允许程序在某些操作完成时,动态执行特定的代码逻辑。
比如下面的例子:
def compute(x, y, callback):
result = x + y
callback(result)
def print_result(value):
print(f"计算结果是:{value}")
compute(3, 4, print_result) # 输出:计算结果是:7
这里 print_result 就是回调函数,当 compute 计算完成后自动执行。
异步编程与回调
编程中,异步可以让程序在等待某些操作(如网络请求或文件读取)时,继续执行其他任务,而不是原地等待。
来看一个结合异步和回调的例子:
import asyncio
async def compute(x, y, callback):
print("开始计算...")
await asyncio.sleep(0.5) # 模拟异步操作
result = x + y
callback(result)
print("计算完成!")
def print_result(value):
print(f"计算结果是:{value}")
async def main():
await compute(3, 4, print_result)
asyncio.run(main())
在这个例子中,await asyncio.sleep(0.5) 模拟了一个耗时操作。当程序执行到这里时,会释放控制权,让其他任务先运行,充分利用时间。
LangChain 中的回调机制
LangChain 为我们提供了非常灵活的回调系统,允许开发者在应用程序的不同阶段自定义逻辑。比如日志记录、流式数据处理或监控模型性能等。
核心组件:CallbackHandler
LangChain 的回调系统基于 CallbackHandler,通过它,我们可以在以下几个阶段插入自定义操作:
- on_llm_start:模型开始生成前触发
- on_llm_new_token:生成新 Token 时触发
- on_llm_end:生成结束后触发
- on_llm_error:发生错误时触发
LangChain 提供了一些现成的回调处理器:
- StdOutCallbackHandler:将事件输出到控制台
- FileCallbackHandler:将事件记录到文件
我们还可以自定义自己的回调逻辑。
自定义回调示例
假如我们要实现一个异步和同步结合的回调,模拟一个“鲜花推荐系统”:
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
# 同步回调:打印生成的每个 Token
class MySyncHandler(BaseCallbackHandler):
def on_llm_new_token(self, token, **kwargs):
print(f"生成的 Token:{token}")
# 异步回调:模拟开始和结束时的延时处理
class MyAsyncHandler(AsyncCallbackHandler):
async def on_llm_start(self, serialized, prompts, **kwargs):
print("开始获取推荐数据...")
await asyncio.sleep(0.5) # 模拟异步操作
print("数据准备完成!")
async def on_llm_end(self, response, **kwargs):
print("处理完成,祝你购物愉快!")
通过这些回调,我们不仅能实时打印生成的 Token,还能在关键环节添加一些模拟操作,增强用户体验。
实战:用回调实现令牌计数器
在与大语言模型交互时,了解 Token 的使用情况对优化成本非常重要。LangChain 提供的 get_openai_callback 工具,让我们可以轻松统计交互中使用的 Token。
令牌计数器示例
我们以一个对话系统为例:
from langchain.callbacks import get_openai_callback
# 使用上下文管理器统计 Tokens
with get_openai_callback() as cb:
conversation("我姐姐明天要过生日,我需要一束生日花束。")
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("\n总计使用的 Tokens:", cb.total_tokens)
运行这段代码后,你会清楚看到这几轮对话一共消耗了多少 Tokens。
结合异步任务实现并发
通过 asyncio,我们可以同时运行多个任务,并统计它们的总 Token 消耗:
import asyncio
async def additional_interactions():
with get_openai_callback() as cb:
await asyncio.gather(
*[llm.agenerate(["适合生日的花是什么?"]) for _ in range(3)]
)
print("\n并发任务的总 Tokens 消耗:", cb.total_tokens)
asyncio.run(additional_interactions())
在这里,asyncio.gather 会并发运行三个任务,充分利用异步编程的优势。
回顾
- 回调函数是什么,它如何帮助我们动态执行代码。
- 异步编程如何结合回调实现并发处理。
- LangChain 的回调系统,包括
CallbackHandler和get_openai_callback的使用。 - 实现了一个实用的令牌计数器,并扩展到异步任务中。