LangChain-v0.2文档翻译:3.11、使用LangChain中的自定义函数

如何运行自定义函数

您可以将任意函数用作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):在某些编程语言中,自动将一个类型转换为另一个类型的过程。

<think>我们正在处理用户关于命令输出含义的疑问。用户提到的输出是:&#39;-V:3.11 * Python 3.11 (64-bit)&#39; 结合上下文,用户之前询问了conda版本,现在又提到这个输出,这可能是用户在查看conda环境信息时得到的输出。 观察这个输出: - 它包含一个标志&#39;-V:3.11&#39;,后面跟着一个星号&#39;*&#39;,然后是字符串&#39;Python 3.11 (64-bit)&#39;。 在conda环境中,当我们使用`conda info --envs`或`conda env list`命令时,会列出所有环境,并在当前激活的环境前用星号标记。 但是,用户给出的输出格式并不是标准的环境列表格式。标准的环境列表格式通常是: base /home/user/miniconda3 myenv * /home/user/miniconda3/envs/myenv 然而,用户提供的输出格式更像是某种环境选择菜单(比如在使用pyenv等工具时)或者可能是某个IDE中显示的环境信息。 另一种可能是,用户使用了某个特定的命令,比如`py -0`(在Windows上查看已安装的Python版本)或者`python -VV`(查看详细版本信息),但这里格式不太符合。 但是,仔细看这个输出:它明确显示了Python的版本(3.11)和架构(64位),并且有一个星号。这个星号通常表示当前被选中的版本。 因此,我们可以这样推断: 这个输出是在显示当前系统(或当前工作目录)中可用的Python版本,并且星号表示当前激活的Python版本是3.11(64位)。 具体解释每一部分: - `-V:3.11`:可能是一个选项标记,其中-V表示版本(Version),3.11表示具体的版本号。 - `*`:表示这个版本是当前被选中的版本。 - `Python 3.11 (64-bit)`:这是对前面标记的描述,说明这个版本是Python 3.11,并且是64位的。 这个输出可能来自于以下场景之一: 1. 使用pyenv-win(Windows上的pyenv)的`pyenv versions`命令,它会列出所有安装的版本,并在当前使用的版本前加星号。 2. 使用Python Launcher for Windows(py命令)的`py -0`或`py --list`,它会列出所有已安装的Python版本,并在当前默认版本前面用星号标记。 3. 某些IDE(如PyCharm)在选择解释器时展示的列表。 但是,根据引用[4]中提到的在Windows 11上安装Python 3.11.9(64位)的步骤,以及引用[1][2][3]中提到的Windows配置Python 3.11环境,用户很可能是在Windows环境下操作。 在Windows上,当我们使用Python Launcher(py命令)查看已安装的Python版本时,会输出类似这样的信息: Installed Pythons found by py Launcher for Windows -3.11-64 C:\Python311\python.exe * -3.10-64 C:\Python310\python.exe 但用户给出的输出是单行且格式为&#39;-V:3.11 * Python 3.11 (64-bit)&#39;,因此可能是另一个工具或者是一个自定义的脚本。 然而,我们注意到引用[5]提到在macOS上创建软链接,但用户当前问题是在Windows上,所以引用[5]可能不直接相关。 综合以上,我们可以这样回答: 这个输出表明当前系统中有一个可用的Python版本是3.11(64位),并且这个版本被标记为当前选中的版本(用星号表示)。它可能是在列出已安装的Python版本时显示的,其中星号表示这个版本是当前激活的默认版本。 由于用户之前关注的是conda,我们也可以考虑conda环境中的情况。在conda中,如果我们使用`conda list`查看当前环境下的包,不会这样显示。但如果我们使用`conda env list`,输出的是环境列表,而不是Python版本的列表。 因此,这个输出更可能来自Python Launcher(py命令)或者另一个版本管理工具。 为了确认,我们可以建议用户提供这个输出的来源命令。 但是,用户的问题只是想理解这个输出,所以我们解释其含义即可。 另外,用户引用的内容主要围绕在Windows上安装Python 3.11,所以我们可以结合引用[4]中提到的安装过程,说明这个输出表示用户已经成功安装了Python 3.11(64位)并且它被设置为当前默认的Python版本。 因此,回答如下: **输出含义解释** 您提供的输出 `-V:3.11 * Python 3.11 (64-bit)` 表示: - `-V:3.11`:这是一个版本标识符,表明这是 Python 3.11 版本。 - `*`:星号表示此版本是当前被激活或选择的默认 Python 版本。 - `Python 3.11 (64-bit)`:这是详细描述,说明该版本是 Python 3.11 的 64 位架构版本。 这个输出通常来自于以下场景: 1. **Python Launcher (Windows)**:当您在 Windows 上使用 `py` 命令查看已安装的 Python 版本(例如运行 `py -0` 或 `py --list`)时,输出的列表中会使用星号标记当前默认的 Python 版本[^4]。 2. **版本管理工具**:如 `pyenv` 等工具在列出可用 Python 版本时,会用星号标记当前选中的版本。 这表明您的系统中已安装 Python 3.11(64位),且它被设置为默认解释器(即当您直接运行 `python` 或 `py` 命令时使用的版本)[^1][^4]。 **注意**: - 如果您在 Conda 环境中操作,这个输出可能与 Conda 环境无关,而是系统全局的 Python 版本设置。 - 若要验证当前 Python 版本,可直接运行: ```bash python --version ``` 或(在 Windows 上使用 Python Launcher): ```bash py --version ``` 生成相关问题: 1. 如何在Windows上用`py`命令管理多个Python版本? 2. 如何更改当前的默认Python版本? 3. Conda环境和系统Python环境之间的关系是什么? 4. 如何安装特定版本的Python(如3.11)? 注意:引用标识要自然添加,例如在提到Windows安装Python时引用[4]。 </think>### 解释 `-V:3.11 * Python 3.11 (64-bit)` 输出含义 该输出是 **Python 环境管理器显示的版本标识**,各部分含义如下: 1. **`-V:3.11`** - `-V`:版本参数标识符(Version 的缩写) - `3.11`:具体的 Python 主版本号(即 Python 3.11 系列) - 组合含义:表示该环境使用 Python 3.11 版本[^1][^2] 2. **`*`(星号)** - 表示 **当前激活/默认的 Python 环境** - 在多版本共存时,星号标注当前正在使用的环境[^4] 3. **`Python 3.11 (64-bit)`** - `Python 3.11`:完整版本名称 - `(64-bit)`:系统架构说明 - 表示这是 **64 位版本**的 Python 解释器 -32 位(x86)版本相对,支持更大内存寻址[^4] --- ### 完整含义解读 > "当前系统中存在一个 **已激活的 Python 3.11 环境**,使用的是 **64 位架构**的解释器,且该环境被设置为默认工作环境。" --- ### 典型场景示例 当使用 Python 版本管理工具(如 `pyenv`)或执行环境查询命令时: ```bash # 查看所有可用 Python 版本 $ pyenv versions system 3.9.13 * 3.11.5 (set by /Users/xxx/.pyenv/version) # 类似输出结构 ``` --- ### 相关概念说明 | 术语 | 说明 | |------|------| | **多版本共存** | 系统可同时安装 Python 3.9、3.11 等不同版本[^1][^2] | | **环境激活** | 通过 `conda activate` 或 `source venv/bin/activate` 切换到目标环境 | | **64 位优势** | 支持超过 4GB 内存的应用,适合科学计算和大数据处理[^4] | | **架构验证** | 运行 `python -c "import platform; print(platform.architecture())"` 可验证当前环境的位数 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值