LangChain:回调函数与异步编程 | 豆包MarsCode AI刷题

47 阅读4分钟

今天和大家一起学习了 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 会并发运行三个任务,充分利用异步编程的优势。


回顾

  1. 回调函数是什么,它如何帮助我们动态执行代码。
  2. 异步编程如何结合回调实现并发处理。
  3. LangChain 的回调系统,包括 CallbackHandlerget_openai_callback 的使用。
  4. 实现了一个实用的令牌计数器,并扩展到异步任务中。