【langchain实战笔记】5、自定义处理链进阶(并行和运行时配置)

292 阅读8分钟

本系列文章是langchain框架v0.3版本的学习实战笔记。

在上一篇文章中我们介绍了如何自定义处理链,并从0开始动手搭建了一个可以基于真实天气进行规划的旅行助手,我们通过串行的方式获取指定城市的天气->生成穿衣指数以及旅行攻略,但其实旅行攻略的生成并以依赖天气信息的返回,如果改为并行处理可以优化旅行助手的响应时效,langchain框架提供了RunnableParallels函数支持Runnable对象的并行处理。

并行调用

我们只需要将代码进行简单修改,就可以快速实现处理流程的并行化,具体代码如下:

import os
os.environ['OPENAI_API_KEY'] = "XXXXXX"
os.environ["OPENAI_API_BASE"] = "XXXXXX"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from operator import itemgetter
from langchain_core.runnables import chain
import requests
from langchain_core.runnables import RunnableParallel

model = ChatOpenAI(
    model="gpt-4o",
    temperature=0.0
)


# 天气情况chain
@chain
def weather_search(dict):
    query = dict["city"] + "天气怎么样"
    top_k = dict["top_k"]
    url = f"https://www.googleapis.com/customsearch/v1?key={YOU_KEY}&cx={YOU_CX}&q={query}".format(
        query=query)
    response = requests.get(url)
    # 只保留有用信息
    result = []
    for item in response.json()["items"]:
        if len(result) >= top_k:
            break
        result.append({"Title": item['title'], "Snippet": item['snippet'], "Link": item['link']})
    return result


# 天气助手
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你的工作是根据用户提供的上线文信息总结指定城市的天气情况,并给出对应的穿衣指数建议,请使用以下回答模板\n 穿衣指数:...|||",
        ),
        ("human", "content:{content},city:{city}"),
    ]
)

weather_assistant = (
        {"content": {"city": itemgetter("city"), "top_k": itemgetter("top_k")} | weather_search,
         "city": itemgetter("city")}
        | prompt
        | model
        | StrOutputParser()
)

# 旅行助手
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你的工作是根据用户输入的城市和旅行天数给出对应的旅行规划建议,请使用以下回答模板\n 规划建议:...|||",
        ),
        ("human", "城市:{city},旅行天数:{day}"),
    ]
)

travel_assistant = (
        {
            "city": itemgetter("city"),
            "day": itemgetter("travel_day")
        }
        | prompt
        | model.bind(stop="|||")
        | StrOutputParser()
)

# 构建并行处理链
parallel_chain = RunnableParallel(weather=weather_assistant, travel=travel_assistant)
parallel_chain.invoke({"city": "北京", "travel_day": "3", "top_k": 3})

在上述示例中,我们使用RunnableParallel函数将 weather_assistant 和 travel_assistant 两个自定义Runnable对象封装成一个新的Runnable对象,当我们调用parallel_chain时,内部的weather_assistant 和 travel_assistant会并行处理。

注意: RunnableParallel 并不会要求并行的子Runnable对象入参一致,但是调用时必须传入子Runnable对象对象的交集。

通过对比发现,并行处理后处理链执行耗时由原来的22.5s降低至6.9s。并行处理链返回结果如下:

{'weather': '穿衣指数: 北京目前天气较冷,白天气温最高为2℃,夜间最低气温为-6℃,并且山区有零星小雪。建议穿着厚实的冬季衣物,如羽绒服、毛衣、保暖裤等,同时注意保暖配件如围巾、手套和帽子。由于昼夜温差较大,外出时需注意及时增减衣物。',
 'travel': '规划建议:\n1. 第一天:\n   - 上午:参观天安门广场和故宫博物院,感受中国古代皇宫的宏伟壮丽。\n   - 下午:游览景山公园,俯瞰故宫全景,然后前往北海公园,欣赏湖光山色。\n   - 晚上:在王府井步行街购物和品尝北京小吃。\n\n2. 第二天:\n   - 上午:前往八达岭长城,体验“不到长城非好汉”的豪情壮志。\n   - 下午:参观明十三陵,了解明朝皇帝的陵寝文化。\n   - 晚上:返回市区,品尝北京烤鸭,推荐全聚德或大董烤鸭店。\n\n3. 第三天:\n   - 上午:游览颐和园,欣赏皇家园林的美景。\n   - 下午:参观圆明园遗址公园,了解其历史背景和文化价值。\n   - 晚上:在南锣鼓巷或后海酒吧街放松,体验北京的夜生活。\n\n希望你在北京度过一个愉快的假期!'}

运行时配置

除了在处理链定义时配置Runnable对象的参数,Langchain还允许在处理链运行时(.invoke()被调用时)对处理链进行配置,例如修改模型参数,或者选择不同模型版本进行对比等。为了实现运行时配置,Langchain提供了两个方法:

  • configurable_fields方法。支持在运行时配置Runnable对象的特定字段,类似于.bind()的用法,但不像.bind()只能事先指定。
  • configurable_alternatives方法。支持在运行时设置Runnable对象的替代方案,即可以通过配置选择要执行的Runnable对象

(1)、运行时配置参数

我们还是以上面的旅行助手为例,例如我们想在运行时配置模型的名称参数。

from langchain_core.runnables import ConfigurableField

model = ChatOpenAI(
    model_name="gpt-4o",
    temperature=0.0
).configurable_fields(
    model_name=ConfigurableField(
        id="model",
        name="大模型名称",
        description="大模型名称",
    )
)

weather_assistant = (
        {"content": {"city": itemgetter("city"), "top_k": itemgetter("top_k")} | weather_search,
         "city": itemgetter("city")}
        | prompt
        | model
)

weather_assistant.invoke({"city": "北京", "top_k": 3})

为了看到更多模型返回的原数据,我们去掉了StrOutputParser()

AIMessage(content='穿衣指数: 北京今天白天最高气温2℃,夜间最低气温-6℃,多云,山区有零星小雪。建议穿着厚重的冬衣,如羽绒服、毛衣、保暖内衣等,外出时注意保暖,特别是早晚温差较大时。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 73, 'prompt_tokens': 307, 'total_tokens': 380, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_f3927aa00d', 'finish_reason': 'stop', 'logprobs': None}, id='run-c2109127-e2a4-4b97-89f6-5bcd1187c036-0', usage_metadata={'input_tokens': 307, 'output_tokens': 73, 'total_tokens': 380, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})

可以看到不配置运行时参数时, 使用的模型为:model_name:gpt-4o。

weather_assistant.with_config(configurable={"model": "gpt-3.5-turbo"}).invoke({"city": "北京", "top_k": 3})
AIMessage(content='根据提供的信息,北京未来一段时间天气晴朗干燥,有冷空气南下,昼夜温差加大,较常年同期偏冷明显。\n\n穿衣指数: 建议穿着保暖衣物,注意增加衣服层次,以防寒冷天气对身体造成影响。|||', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 114, 'prompt_tokens': 408, 'total_tokens': 522, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': 'fp_0165350fbb', 'finish_reason': 'stop', 'logprobs': None}, id='run-ceeb851e-d0d4-47ec-8d08-15e3d8e41baa-0', usage_metadata={'input_tokens': 408, 'output_tokens': 114, 'total_tokens': 522, 'input_token_details': {}, 'output_token_details': {}})

配置运行时参数后, 使用的模型为:model_name:gpt-3.5-turbo。

(2)、运行时配置可运行对象

我们还是以上面的旅行助手为例,通过配置可运行对象实现对于不同版本模型的选择。

from langchain_core.runnables import ConfigurableField

model = ChatOpenAI(
    model_name="gpt-4o",
    temperature=0.0
).configurable_alternatives(
    ConfigurableField(id="llm"),
    default_key="model",
    gpt_35_turbo=ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0),
)

weather_assistant.with_config(configurable={"llm": "gpt_35_turbo"}).invoke({"city": "北京", "top_k": 3})
AIMessage(content='根据提供的信息,北京未来一段时间天气晴朗干燥,气温较常年同期偏冷明显,白天最高气温2℃,夜间最低气温-6℃,山区有零星小雪。建议穿着保暖,多层穿搭,注意防寒保暖。\n\n穿衣指数: 寒冷,建议穿厚外套、羽绒服、围巾手套等保暖服装。|||', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 155, 'prompt_tokens': 408, 'total_tokens': 563, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_0165350fbb', 'finish_reason': 'stop', 'logprobs': None}, id='run-40a6463f-2c6d-4810-bad5-fea111a75d90-0', usage_metadata={'input_tokens': 408, 'output_tokens': 155, 'total_tokens': 563, 'input_token_details': {}, 'output_token_details': {}})

配置运行时参数后, 使用的模型为:model_name:gpt-3.5-turbo。

后记

在运行时配置这一小节的实战部分,我尝试使用自定义的Runnable对象支持运行时配置,一方面由于自定义Runnable对象只能通过函数生成,无法像ChatOpenAI对象一样有初始化参数可以配置,另一方面,RunnableLambda对象不支持 configurable_fieldsconfigurable_alternatives 方法。

import requests
from langchain_core.runnables import RunnableLambda
from langchain_core.runnables import ConfigurableField
def google_search(dict):
    query = dict["city"]+"天气怎么样"
    top_k = dict["top_k"]
    url = f"https://www.googleapis.com/customsearch/v1?key={YOU_KEY}&cx={YOU_CX}&q={query}".format(
        query=query)
    response = requests.get(url)
    # 只保留有用信息
    result = []
    for item in response.json()["items"]:
        if len(result)>=top_k:
            break
        result.append({"Title": item['title'], "Snippet": item['snippet'], "Link": item['link']})
    return result


weather_search_chain = RunnableLambda(google_search)
weather_search_chain.configurable_alternatives(
    ConfigurableField(id="search_chain"),
    default_key="weather_search_chain",
)

报错:

AttributeError: 'RunnableLambda' object has no attribute 'configurable_fields'

AttributeError: 'RunnableLambda' object has no attribute 'configurable_alternatives'

因此目前结论是:Langchain自定义Runnable对象并不支持运行时配置,但是可以通过传参的方式,达到一样的效果,如果JYM有其他实现途径,欢迎一起讨论!!!!

请教.gif

系列文章

【langchain实战笔记】1、构建简单LLM应用
【langchain实战笔记】2、LLM服务限速参数
【langchain实战笔记】3、构建自定义多工具智能体
【langchain实战笔记】4、自定义处理链基础(基于天气信息的旅行助手)