【AI大模型应用开发】【LangChain系列】1. 全面学习LangChain输入输出I/O模块:理论介绍+实战示例+细节注释

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【AI大模型应用开发】【LangChain系列】1. 全面学习LangChain输入输出I/O模块:理论介绍+实战示例+细节注释

上文我们介绍过LangChain的基本框架和其中包含的主要模块。从今天开始,我们开始学习各个模块,深入了解,同时进行相应实战练习。

本文学习 LangChain 中的 模型 I/O 封装模块。

0. 模块介绍

任何AI大模型应用程序的核心元素都是大模型。LangChain提供了与各种大模型接口进行交互的封装。

这张图生动地展现了LangChain对于I/O(输入输出)的封装。

  • 首先是 Format 部分,这部分的作用是组装用户输入和Prompt模板,作为大模型的输入。
  • 然后是 Predict 部分,这部分就是调用大模型接口获得结果
  • 最后是 Parse 部分,这部分的作用是对大模型的结果进行解析,将大模型的输出转换到要求的格式(如json)上,或者对输出进行校验等等

1. Format部分:Prompt模板封装

1.1 PromptTemplate:创建一个字符串类型的Prompt

PromptTemplate 可以在模板中自定义变量

import os
# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI() # 默认是gpt-3.5-turbo
prompt_template = """
我的名字叫【{name}】,我的个人介绍是【{description}】。
请根据我的名字和介绍,帮我想一段有吸引力的自我介绍的句子,以此来吸引读者关注和点赞我的账号。
"""
from langchain.prompts import PromptTemplate
template = PromptTemplate.from_template(prompt_template)
print(template.input_variables)
prompt = template.format(name='同学小张', description='热爱AI,持续学习,持续干货输出')
print(prompt)
response = llm.invoke(prompt)
print(response.content)

1.2 ChatPromptTemplate:创建一个Prompt的Message数组

...... 省略llm的引入代码,可参考前文 ......
from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate
template = ChatPromptTemplate.from_messages(
    [
        SystemMessagePromptTemplate.from_template("你是【{name}】的个人助手,你需要根据用户输入,来替用户生成一段有吸引力的自我介绍的句子,以此来吸引读者关注和点赞用户的账号。"),
        HumanMessagePromptTemplate.from_template("{description}"),
    ]
)
prompt = template.format(name="同学小张", description="热爱AI,持续学习,持续干货输出")
print(prompt)
response = llm.invoke(prompt)
print(response.content)

运行后输出结果如下,可以看到Prompt中带入了 System、Human这样的角色名,区分Prompt的来源。

1.3 FewShotPromptTemplate:给例子的Prompt模板

在之前文章Prompt优化中,我们提到Prompt中给几个例子可以让大模型更好地生成正确的结果。这个模板就是给例子的。

from langchain.prompts import PromptTemplate
from langchain.prompts.few_shot import FewShotPromptTemplate
#例子(few-shot)
examples = [
    {
        "input": "北京天气怎么样",
        "output" : "北京市"
    },
    {
        "input": "南京下雨吗",
        "output" : "南京市"
    },
    {
        "input": "江城热吗",
        "output" : "武汉市"
    }
]
#例子拼装的格式
example_prompt = PromptTemplate(input_variables=["input", "output"],  template="Input: {input}\nOutput: {output}") 
#Prompt模板
prompt = FewShotPromptTemplate(
    examples=examples, 
    example_prompt=example_prompt, 
    suffix="Input: {input}\nOutput:", 
    input_variables=["input"]
)
prompt = prompt.format(input="羊城多少度")
print("===Prompt===")
print(prompt)
response = llm.invoke(prompt)
print("===Response===")
print(response)

以上代码为FewShotPromptTemplate的使用示例,总结为以下关键点:

  • 例子(few-shot)用数组表示:examples
  • 用PromptTemplate表示examples中的格式:Input后跟着output,注意:input_variables中的变量与examples中每个元素的key保持一致。
  • 通过 FewShotPromptTemplate 将以上元素组合起来
  • 同时传入 suffix 参数,该参数是接收用户的输入,组装提问的prompt模板。
  • 然后input_variables表示用户输入的参数变量名

运行结果如下:红框内是通过FewShotPromptTemplate 将examples、example_prompt、suffix组合起来后最终的给大模型的Prompt。

1.4 从文件加载Prompt模板

我们还可以将Prompt模板单独存放在一个文件中,在程序运行时通过加载文件来导入Prompt模板。

这种方式很好地实现了 Prompt 和程序的分离,使得两者可以分别单独修改。甚至你可以将Prompt单独放在一个线上服务或数据库中,单独维护。

下面来看怎么实现。

1.4.1 Prompt模板文件格式

Prompt模板文件支持两种格式:yaml格式和json格式

  • yaml格式:
_type: prompt
input_variables:
    ["name", "description"]
template: 
    我的名字叫【{name}】,我的个人介绍是【{description}】。\n 请根据我的名字和介绍,帮我想一段有吸引力的自我介绍,以此来吸引读者关注和点赞我的账号。
  • json格式
{
    "_type": "prompt",
    "input_variables": ["name", "description"],
    "template": "我的名字叫【{name}】,我的个人介绍是【{description}】。\n 请根据我的名字和介绍,帮我想一段有吸引力的自我介绍,以此来吸引读者关注和点赞我的账号。"
}
1.4.2 加载文件

使用 LangChain的load_prompt进行加载。

from langchain.prompts import load_prompt
prompt = load_prompt("D:\GitHub\LEARN_LLM\langchain\langchain_prompt_file_test.json")
prompt_str = prompt.format(name="同学小张", description="热爱AI,持续学习,持续干货输出")
print(prompt_str)
response = llm.invoke(prompt_str)
print(f"\n{response}")

1.4.3 更进一步:文件套文件

LangChain也允许你在Prompt文件中再套Prompt文件:将文件中的template字段单独放一个txt文件使用。拆分后文件如下:

  • prompt_template_test.txt
我的名字叫【{name}】,我的个人介绍是【{description}】。\n 请根据我的名字和介绍,帮我想一段有吸引力的自我介绍,以此来吸引读者关注和点赞我的账号。
  • langchain_prompt_file_test.json
{
    "_type": "prompt",
    "input_variables": ["name", "description"],
    "template_path": "D:\\GitHub\\LEARN_LLM\\langchain\\prompt_template_test.txt"
}

注意:json里面的template字段换成了template_path字段

1.5 其它Prompt模板

还有一些其它的Prompt模板,就不详细介绍了,都差不多。

  • FewShotChatMessagePromptTemplate
  • ChatMessagePromptTemplate:可以自定义Prompt的角色名,如之前的“System”、“AI”、“Human”都是角色。

该部分参考:https://python.langchain.com/docs/modules/model_io/prompts/

总结:把Prompt模板看作带有参数的函数

2. Predict部分:大模型接口封装

这部分主要看下LangChain对大模型的两种封装:llm 和 chat_model。

from langchain_openai import ChatOpenAI
from langchain_openai import OpenAI
llm = OpenAI()
chat_model = ChatOpenAI()
from langchain.schema import HumanMessage
text = "What would be a good company name for a company that makes colorful socks?"
messages = [HumanMessage(content=text)]
llm.invoke(text)
# >> Feetful of Fun
chat_model.invoke(messages)
# >> AIMessage(content="Socks O'Color")

可以看到 llm 和 chat_model 的区别,一个输出字符串,一个输出message。

3. Parse部分:输出结果校验的封装

LangChain封装了一些对于大模型输出结果的约定和校验能力。下面以PydanticOutputParser为例演示一下Parse部分的使用方法和作用。

3.1 使用步骤

(1)首先定义一个你期望返回的数据结构

下面代码中,我们定义了一个Joke数据结构,它里面包含的信息有:

  • 两个变量名:setup 和 punchline,大模型的返回需要以这两个名称作为key来组织答案
  • 一个自定义的校验函数:question_ends_with_question_mark,校验信息是否符合你的要求,如果不符合,则报错。

@validator("setup") 表示校验结果中的setup字段。也就是说,首先大模型回复的答案中,首先必须是个json结构,才能解析出setup的内容。其次,json数据结构中必须有setup的字段。最后,setup的内容必须符合函数中定义的规则。这样才算通过,否则报错。

from langchain_core.pydantic_v1 import BaseModel, Field, validator
# 定义你期望的数据结构
class Joke(BaseModel):
    setup: str = Field(description="question to set up a joke")
    punchline: str = Field(description="answer to resolve the joke")
    # 使用Pydantic添加自定义的校验逻辑,如下为检测内容最后一个字符是否为问号,不为问号则提示错误.
    @validator("setup")
    def question_ends_with_question_mark(cls, field):
        if field[-1] != "?":
            raise ValueError("Badly formed question!")
        return field

(2)生成一个解析器的实例

parser = PydanticOutputParser(pydantic_object=Joke)

(3)生成 Prompt 模板

在这个Prompt模板中:

  • 通过template指定Prompt的框架
  • input_variables指定用户输入的信息放到这个变量名中
  • partial_variables是提前填充部分Prompt变量,这里通过parser.get_format_instructions()获取PydanticOutputParser中封住好的Prompt部分。
prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

看下parser.get_format_instructions()的内容:

The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {“properties”: {“foo”: {“title”: “Foo”,

“description”: “a list of strings”, “type”: “array”, “items”: {“type”:

“string”}}}, “required”: [“foo”]} the object {“foo”: [“bar”, “baz”]}

is a well-formatted instance of the schema. The object {“properties”:

{“foo”: [“bar”, “baz”]}} is not well-formatted.

Here is the output schema: ```{“properties”: {“setup”: {“title”:
“Setup”, “description”: “question to set up a joke”, “type”:
“string”}, “punchline”: {“title”: “Punchline”, “description”: “answer
to resolve the joke”, “type”: “string”}}, “required”: [“setup”,
“punchline”]} ```

可以看到,LangChain内部将咱们上面定义的Joke数据结构填到了里面,并要求大模型输出json结构。

(4)加上用户的提问,调用大模型获取回复

prompt_str = prompt.format(query="Tell me a joke.")
response = llm.invoke(prompt_str)

完整Prompt如下:

运行结果如下:

(5)校验输出结果是否符合要求

parser_result = parser.invoke(response) ## 调用parser的invoke,校验结果是否符合要求

上面的结果明显符合要求,最终输出如下:

#>> setup="Why don't scientists trust atoms?" punchline='Because they make up everything!'

3.2 不符合要求的情况

为了看一下不符合要求时会发生什么,我在大模型返回后手动改了下结果,让它不符合要求(要求是问句结尾必须是问号,下面我将问号删掉了)。

运行结果:报错了

如果大模型返回的结果不是json结构,也会报错:

3.3 不符合要求怎么办?Auto-Fixing Parser帮你自动修复错误

基本用法如下:

## 1. 引入OutputFixingParser
from langchain.output_parsers import OutputFixingParser
## 2. 使用之前的parser和llm,构建一个OutputFixingParser实例
new_parser = OutputFixingParser.from_llm(parser=parser, llm=llm)
## 3. 用OutputFixingParser自动修复并解析
parser_result = new_parser.parse(response.content)
print("===重新解析结果===")
print(parser_result)

为了展示它的效果,我还是手动将结果改错了。

输出结果如下:可以看到重新解析后结果正确了。

重新解析为什么就正确了?其实是OutputFixingParser内部又重新调用了一遍大模型

3.4 完整代码

import os
# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI() # 默认是gpt-3.5-turbo
def output_parse_test():
    from langchain.output_parsers import PydanticOutputParser
    from langchain_core.pydantic_v1 import BaseModel, Field, validator
    from langchain.prompts import PromptTemplate
    
    # 定义你期望的数据结构
    class Joke(BaseModel):
        setup: str = Field(description="question to set up a joke")
        punchline: str = Field(description="answer to resolve the joke")
        # 使用Pydantic添加自定义的校验逻辑,如下为检测内容最后一个字符是否为问号,不为问号则提示错误.
        @validator("setup")
        def question_ends_with_question_mark(cls, field):
            if field[-1] != "?":
                raise ValueError("Badly formed question!")
            return field
        
    # 生成一个解析器的实例
    parser = PydanticOutputParser(pydantic_object=Joke)
    
    # 生成 Prompt 模板
    prompt = PromptTemplate(
        template="Answer the user query.\n{format_instructions}\n{query}\n",
        input_variables=["query"],
        partial_variables={"format_instructions": parser.get_format_instructions()},
    )
    print(f"\n{parser.get_format_instructions()}")
    prompt_str = prompt.format(query="Tell me a joke.")
    print(prompt_str)
    response = llm.invoke(prompt_str)
    print(f"\n{response.content}")
    # response.content = response.content.replace("?", "") ## 认为改错结果,测试后面的OutputFixingParser
    try:
        parser_result = parser.invoke(response)
        print(f"\n{parser_result}")
    except Exception as e:
        print("===出现异常===")
        print(e)
        ## 1. 引入OutputFixingParser
        from langchain.output_parsers import OutputFixingParser
        ## 2. 使用之前的parser和llm,构建一个OutputFixingParser实例
        new_parser = OutputFixingParser.from_llm(parser=parser, llm=llm)
        ## 3. 用OutputFixingParser自动修复并解析
        parser_result = new_parser.parse(response.content)
        print("===重新解析结果===")
        print(parser_result)
output_parse_test()

关于更多 OutputParser 的说明,可以看官方文档:https://python.langchain.com/docs/modules/model_io/output_parsers/

4. 总结

本文我们全面学习了LangChain的模型 I/O 封装模块。

  • LangChain 提供了各种 PromptTemplate 类,可以自定义带变量的模板
  • LangChain 统一封装了各种模型的调用接口,包括llm型和chat_model型两种,区别见上文。
  • LangChain 提供了一系列输出解析器,用于将大模型的输出解析成结构化对象;额外带有自动修复功能。

如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~


  • 大家好,我是同学小张
  • 欢迎 点赞 + 关注 👏,促使我持续学习持续干货输出
  • +v: jasper_8017 一起交流💬,一起进步💪。
  • 微信公众号也可搜【同学小张】 🙏
  • 踩坑不易,感谢关注和围观

本站文章一览:

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
|
2月前
|
机器学习/深度学习 人工智能 并行计算
"震撼!CLIP模型:OpenAI的跨模态奇迹,让图像与文字共舞,解锁AI理解新纪元!"
【10月更文挑战第14天】CLIP是由OpenAI在2021年推出的一种图像和文本联合表示学习模型,通过对比学习方法预训练,能有效理解图像与文本的关系。该模型由图像编码器和文本编码器组成,分别处理图像和文本数据,通过共享向量空间实现信息融合。CLIP利用大规模图像-文本对数据集进行训练,能够实现zero-shot图像分类、文本-图像检索等多种任务,展现出强大的跨模态理解能力。
125 2
|
2月前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
352 2
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
当语言遇见智慧火花:GPT家族历代模型大起底,带你见证从平凡到卓越的AI进化奇迹!
【10月更文挑战第6天】随着自然语言处理技术的进步,GPT系列模型(Generative Pre-trained Transformers)成为该领域的明星。从GPT-1的开创性工作,到GPT-2在规模与性能上的突破,再到拥有1750亿参数的GPT-3及其无需微调即可执行多种NLP任务的能力,以及社区驱动的GPT-NeoX,这些模型不断进化。虽然它们展现出强大的语言理解和生成能力,但也存在如生成错误信息或偏见等问题。本文将对比分析各代GPT模型的特点,并通过示例代码展示其部分功能。
124 2
|
2月前
|
存储 人工智能 搜索推荐
解锁AI新境界:LangChain+RAG实战秘籍,让你的企业决策更智能,引领商业未来新潮流!
【10月更文挑战第4天】本文通过详细的实战演练,指导读者如何在LangChain框架中集成检索增强生成(RAG)技术,以提升大型语言模型的准确性与可靠性。RAG通过整合外部知识源,已在生成式AI领域展现出巨大潜力。文中提供了从数据加载到创建检索器的完整步骤,并探讨了RAG在企业问答系统、决策支持及客户服务中的应用。通过构建知识库、选择合适的嵌入模型及持续优化系统,企业可以充分利用现有数据,实现高效的商业落地。
91 6
|
4天前
|
机器学习/深度学习 人工智能 语音技术
Fugatto:英伟达推出的多功能AI音频生成模型
Fugatto是由英伟达推出的多功能AI音频生成模型,能够根据文本提示生成音频或视频,并修改现有音频文件。该模型基于增强型的Transformer模型,支持复杂的组合指令,具有强大的音频生成与转换能力,广泛应用于音乐创作、声音设计、语音合成等领域。
42 1
Fugatto:英伟达推出的多功能AI音频生成模型
|
1月前
|
人工智能
AI科学家太多,谁靠谱一试便知!普林斯顿新基准CORE-Bench:最强模型仅有21%准确率
【10月更文挑战第21天】普林斯顿大学研究人员提出了CORE-Bench,一个基于计算可重复性的AI代理基准,涵盖计算机科学、社会科学和医学领域的270个任务。该基准旨在评估AI代理在科学研究中的准确性,具有多样性、难度级别和现实相关性等特点,有助于推动AI代理的发展并提高计算可重复性。
48 4
|
2月前
|
人工智能 自然语言处理
从迷茫到精通:揭秘模型微调如何助你轻松驾驭AI新热点,解锁预训练模型的无限潜能!
【10月更文挑战第13天】本文通过简单的问题解答形式,结合示例代码,详细介绍了模型微调的全流程。从选择预训练模型、准备新任务数据集、设置微调参数,到进行微调训练和评估调优,帮助读者全面理解模型微调的技术细节和应用场景。
74 6
|
2月前
|
机器学习/深度学习 人工智能 开发框架
解锁AI新纪元:LangChain保姆级RAG实战,助你抢占大模型发展趋势红利,共赴智能未来之旅!
【10月更文挑战第4天】本文详细介绍检索增强生成(RAG)技术的发展趋势及其在大型语言模型(LLM)中的应用优势,如知识丰富性、上下文理解和可解释性。通过LangChain框架进行实战演练,演示从知识库加载、文档分割、向量化到构建检索器的全过程,并提供示例代码。掌握RAG技术有助于企业在问答系统、文本生成等领域把握大模型的红利期,应对检索效率和模型融合等挑战。
173 14
|
2月前
|
人工智能 前端开发 JavaScript
前端大模型入门(二):掌握langchain的核心Runnable接口
Langchain.js 是 Langchain 框架的 JavaScript 版本,专为前端和后端 JavaScript 环境设计。最新 v0.3 版本引入了强大的 Runnable 接口,支持灵活的执行方式和异步操作,方便与不同模型和逻辑集成。本文将详细介绍 Runnable 接口,并通过实现自定义 Runnable 来帮助前端人员快速上手。
|
2月前
|
人工智能 自然语言处理 安全
【通义】AI视界|Adobe推出文生视频AI模型,迎战OpenAI和Meta
本文精选了过去24小时内的重要科技新闻,包括微软人工智能副总裁跳槽至OpenAI、Adobe推出文本生成视频的AI模型、Meta取消高端头显转而开发超轻量设备、谷歌与核能公司合作为数据中心供电,以及英伟达股价创下新高,市值接近3.4万亿美元。这些动态展示了科技行业的快速发展和激烈竞争。点击链接或扫描二维码获取更多资讯。