如何运行自定义函数
您可以将任意函数用作Runnables。这在需要格式化或当其他LangChain组件未提供所需功能时非常有用,用作Runnables的自定义函数称为RunnableLambdas
。
请注意,所有输入到这些函数的参数必须是单个参数。如果您有一个接受多个参数的函数,您应该编写一个包装器,该包装器接受单个字典输入并将其解包为多个参数。
本指南将涵盖:
- 如何使用
RunnableLambda
构造器和方便的@chain
装饰器显式创建自定义函数的可运行实例 - 在链中使用时将自定义函数强制转换为可运行实例
- 如何在自定义函数中接受和使用运行元数据
- 如何通过使它们返回生成器来使用自定义函数进行流式处理
使用构造器
下面,我们使用RunnableLambda
构造器显式地包装我们的自定义逻辑:
# 安装LangChain和langchain_openai库
%pip install -qU langchain langchain_openai
import os
from getpass import getpass
# 设置环境变量以存储OpenAI的API密钥
os.environ["OPENAI_API_KEY"] = getpass()
from operator import itemgetter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
# 定义一个返回文本长度的函数
def length_function(text):
return len(text)
# 定义一个接受两个文本并返回它们长度乘积的函数
def _multiple_length_function(text1, text2):
return len(text1) * len(text2)
# 定义一个包装函数,它接受一个字典并调用_multiple_length_function
def multiple_length_function(_dict):
return _multiple_length_function(_dict["text1"], _dict["text2"])
# 创建ChatOpenAI模型实例
model = ChatOpenAI()
# 创建一个聊天提示模板
prompt = ChatPromptTemplate.from_template("what is {a} + {b}")
# 创建一个链,其中包含RunnableLambda实例
chain1 = prompt | model
# 定义一个更复杂的链,使用itemgetter和RunnableLambda
chain = (
{
"a": itemgetter("foo") | RunnableLambda(length_function),
"b": {"text1": itemgetter("foo"), "text2": itemgetter("bar")}
| RunnableLambda(multiple_length_function),
}
| prompt
| model
)
# 调用链并传入字典参数
chain.invoke({"foo": "bar", "bar": "gah"})
AIMessage(content='3 + 9 equals 12.', response_metadata={'token_usage': {'completion_tokens': 8, 'prompt_tokens': 14, 'total_tokens': 22}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-73728de3-e483-49e3-ad54-51bd9570e71a-0')
便捷 @chain
装饰器
您也可以通过添加 @chain
装饰器将任意函数转换为链。这在功能上等同于上面展示的用 RunnableLambda
构造器包装函数。以下是一个示例:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import chain
# 定义两个聊天提示模板
prompt1 = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
prompt2 = ChatPromptTemplate.from_template("What is the subject of this joke: {joke}")
# 使用@chain装饰器将custom_chain函数转换为可运行的链
@chain
def custom_chain(text):
# 使用第一个提示模板和模型生成笑话
prompt_val1 = prompt1.invoke({"topic": text})
output1 = ChatOpenAI().invoke(prompt_val1)
# 使用字符串输出解析器解析笑话内容
parsed_output1 = StrOutputParser().invoke(output1)
# 创建第二个链,用于解析笑话的主题
chain2 = prompt2 | ChatOpenAI() | StrOutputParser()
# 调用第二个链并返回结果
return chain2.invoke({"joke": parsed_output1})
# 调用custom_chain函数并传入"bears"作为参数
custom_chain.invoke("bears")
# 输出结果示例
'The subject of the joke is the bear and his girlfriend.'
上面, @chain 装饰器用于将 custom_chain 转换为可运行对象,我们使用 .invoke() 方法调用它。
如果您使用 LangSmith 进行跟踪,您应该会在其中看到一个 custom_chain 跟踪,并且对 OpenAI 的调用嵌套在下面。
自动转换在链中
在使用自定义函数与管道操作符 (|
) 结合使用时,您可以省略 RunnableLambda
或 @chain
构造器,并依赖于自动转换。以下是一个简单的示例,该示例中的函数接收模型的输出并返回其前五个字符:
# 定义一个聊天提示模板
prompt = ChatPromptTemplate.from_template("tell me a story about {topic}")
# 创建ChatOpenAI模型实例
model = ChatOpenAI()
# 使用管道操作符创建一个包含自动转换函数的链
chain_with_coerced_function = prompt | model | (lambda x: x.content[:5])
# 调用链并传入参数
chain_with_coerced_function.invoke({"topic": "bears"})
传递运行元数据
Runnable lambda 可以选择接受 RunnableConfig 参数,它们可以使用该参数将回调、标签和其他配置信息传递给嵌套运行。
import json
from langchain_core.runnables import RunnableConfig
def parse_or_fix(text: str, config: RunnableConfig):
fixing_chain = (
ChatPromptTemplate.from_template(
"Fix the following text:\n\n```text\n{input}\n```\nError: {error}"
" Don't narrate, just respond with the fixed data."
)
| model
| StrOutputParser()
)
for _ in range(3):
try:
return json.loads(text)
except Exception as e:
text = fixing_chain.invoke({"input": text, "error": e}, config)
return "Failed to parse"
from langchain_community.callbacks import get_openai_callback
with get_openai_callback() as cb:
output = RunnableLambda(parse_or_fix).invoke(
"{foo: bar}", {"tags": ["my-tag"], "callbacks": [cb]}
)
print(output)
print(cb)
{'foo': 'bar'}
Tokens Used: 62
Prompt Tokens: 56
Completion Tokens: 6
Successful Requests: 1
Total Cost (USD): $9.6e-05
from langchain_community.callbacks import get_openai_callback
with get_openai_callback() as cb:
output = RunnableLambda(parse_or_fix).invoke(
"{foo: bar}", {"tags": ["my-tag"], "callbacks": [cb]}
)
print(output)
print(cb)
{'foo': 'bar'}
Tokens Used: 62
Prompt Tokens: 56
Completion Tokens: 6
Successful Requests: 1
Total Cost (USD): $9.6e-05
流式处理
注意:
RunnableLambda
最适合于不需要支持流式处理的代码。如果您需要支持流式处理(即能够操作输入的块并产生输出的块),则应使用 RunnableGenerator
,如下例所示。
您可以在链中使用生成器函数(即使用 yield
关键字并表现得像迭代器的函数)。
这些生成器的签名应为 Iterator[Input] -> Iterator[Output]
。或者对于异步生成器:AsyncIterator[Input] -> AsyncIterator[Output]
。
这些对于以下情况非常有用:
- 实现自定义输出解析器
- 在保留流式能力的同时修改前一步骤的输出
以下是一个自定义输出解析器的示例,用于逗号分隔的列表。首先,我们创建一个生成此类列表文本的链:
from typing import Iterator, List
# 创建一个聊天提示模板
prompt = ChatPromptTemplate.from_template(
"Write a comma-separated list of 5 animals similar to: {animal}. Do not include numbers"
)
# 创建一个链,使用模型和字符串输出解析器生成逗号分隔的列表
str_chain = prompt | model | StrOutputParser()
# 使用流式处理输出
for chunk in str_chain.stream({"animal": "bear"}):
print(chunk, end="", flush=True)
lion, tiger, wolf, gorilla, panda
接下来,我们定义一个自定义函数,该函数将聚合当前流式输出,并在模型生成列表中的下一个逗号时产生它:
# 这是一个自定义解析器,它将迭代器中的llm标记拆分为逗号分隔的字符串列表
def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:
# 保存部分输入直到我们得到一个逗号
buffer = ""
for chunk in input:
# 将当前块添加到缓冲区
buffer += chunk
# 只要缓冲区中有逗号
while "," in buffer:
# 在逗号处拆分缓冲区
comma_index = buffer.index(",")
# 产生逗号之前的所有内容
yield [buffer[:comma_index].strip()]
# 保存下一部分以供下一次迭代使用
buffer = buffer[comma_index + 1:]
# 产生最后一个块
yield [buffer.strip()]
# 使用自定义解析器创建链
list_chain = str_chain | split_into_list
# 使用流式处理输出
for chunk in list_chain.stream({"animal": "bear"}):
print(chunk, flush=True)
['lion']
['tiger']
['wolf']
['gorilla']
['raccoon']
调用它将得到一个完整的值数组:
list_chain.invoke({"animal": "bear"})
['lion', 'tiger', 'wolf', 'gorilla', 'raccoon']
异步版本
如果您在异步环境中工作,以下是上述示例的异步版本:
from typing import AsyncIterator
# 定义一个异步函数,用于拆分输入为逗号分隔的列表
async def asplit_into_list(input: AsyncIterator[str]) -> AsyncIterator[List[str]]:
buffer = ""
async for chunk in input:
buffer += chunk
while "," in buffer:
comma_index = buffer.index(",")
yield [buffer[:comma_index].strip()]
buffer = buffer[comma_index + 1:]
yield [buffer.strip()]
# 使用异步链和流式处理
list_chain = str_chain | asplit_into_list
# 异步流式处理输出
async for chunk in list_chain.astream({"animal": "bear"}):
print(chunk, flush=True)
['lion']
['tiger']
['wolf']
['gorilla']
['panda']
# 异步调用链
await list_chain.ainvoke({"animal": "bear"})
['lion', 'tiger', 'wolf', 'gorilla', 'panda']
总结:
本文详细介绍了如何在LangChain框架中使用自定义函数作为Runnables,包括使用构造器、装饰器、自动转换和流式处理。通过示例代码,我们学习了如何显式创建可运行的自定义函数,如何将自定义函数转换为Runnables,以及如何在自定义函数中使用运行元数据和流式处理。
扩展知识点:
-
流式处理 (Streaming):一种数据处理方式,允许逐块处理数据,而不是一次性处理整个数据集。
-
异步编程 (Async Programming):一种编程范式,允许程序在等待操作完成时继续执行,提高了程序的效率。
-
生成器 (Generator):Python中的一种特殊函数,可以逐个产生值,而不是一次性返回所有值。
-
装饰器 (
@chain
):在Python中,装饰器是一种设计模式,用于在不修改函数代码的情况下增加函数功能。 -
Lambda 函数:Python中的一种简洁定义小函数的方式,常用于需要函数对象的地方。
-
自动类型转换 (Coercion):在某些编程语言中,自动将一个类型转换为另一个类型的过程。