langchain开发框架实践(一)

Posted by     "虞天" on Tuesday, August 6, 2024

1、langchain是什么

LangChain是一个基于LLM开发应用程序的框架,把调用LLM的过程组成一条链的形式,具体要执行哪些函数是由LLM的推理结果决定的。(区别于传统程序是写死的)同时LangChain也是一个丰富的工具生态系统的一部分,我们可以在此框架集成并在其之上构建自己的Agent。

LangChain的模块组成:

  • Model I/O(与语言模型进行接口)
  • Retriever(与特定于应用程序的数据进行接口)
  • Memory(在Pipeline运行期间保持记忆状态)
  • Chain(构建调用序列链条)
  • Agent(让管道根据高级指令选择使用哪些工具)
  • Callback(记录和流式传输任何管道的中间步骤)

2、模块 Chain

LangChain应用程序的核心构建模块是LLMChain。它结合了三个方面:

  • LLM: 语言模型是核心推理引擎。要使用LangChain,您需要了解不同类型的语言模型以及如何使用它们。
  • Prompt Templates: 提供语言模型的指令。这控制了语言模型的输出,因此了解如何构建提示和不同的提示策略至关重要。
  • Output Parsers: 将LLM的原始响应转换为更易处理的格式,使得在下游使用输出变得容易。

每个Langchain组件都是LCEL对象,我们可以使用LangChain 表达式语句(LCEL)轻松的将各个组件链接在一起,如下实现prompt + model + output parser的chain = prompt | llm | output_parser,其中| 符号可以实现将数据从一个组件提供的输出,输入到下一个组件中:

我们结合代码进行具体的说明

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 定义提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a world-class technical documentation writer."),
    ("user", "{input}")
])

# 填充模型名
llm = ChatOpenAI(
    model_name='qwen1.5-32b-chat-int4',
    openai_api_base='http://20.20.136.251:8001/v1',
    openai_api_key='q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2'
)

# 定义输出解析器
output_parser = StrOutputParser()

# 创建链条
chain = prompt | llm | output_parser

# 生成 prompt 的值
prompt_value = prompt.invoke({"input": "how can langsmith help with testing?"})
print(prompt_value)

# 通过链条获取模型的响应
response = chain.invoke(prompt_value)

# 打印模型响应
print(response)

接下来仔细看一些下着三个组件的输入和输出:

prompt

  • prompt是一个 ChatPromptTemplate,这意味着它接受模板变量的字典并生成 PromptValue。PromptValue是完整提示的包装器,可以传递给 LLM (将字符串作为输入)或ChatModel(将一系列消息作为输入)。
  • 打印出来可以看到,prompt_value 是一个ChatPromptValue对象,里面的message是一个list,包含不同角色message的对话信息。

model

  • 如果model为 ChatModel,这意味着它将输出 BaseMessage。而如果我们的model是 LLM,它将输出一个字符串。

output_parser

  • 最后,我们将model输出传递给output_parser,这意味着 BaseOutputParser它需要字符串或 BaseMessage 作为输入。StrOutputParser是将任何输入转换为字符串。

3、模块 Model I/O

首先我们从最基本面的部分讲起,Model I/O 指的是和LLM直接进行交互的过程。

3.1、模块 Prompt Templates

  • Prompt:
    • 指用户的一系列指令和输入,是决定Language Model输出内容的唯一输入,主要用于帮助模型理解上下文,并生成相关和连贯的输出,如回答问题、拓写句子和总结问题。
  • Prompt Template:
    • 预定义的一系列指令和输入参数的prompt模版(默认使用str.fromat格式化),支持更加灵活的输入,如支持output instruction(输出格式指令), partial input(提前指定部分输入参数), examples(输入输出示例)等;LangChain提供了大量方法来创建Prompt Template,有了这一层组件就可以在不同Language Model和不同Chain下大量复用Prompt Template了。
  • Example selectors:
    • 在很多场景下,单纯的instruction + input的prompt不足以让LLM完成高质量的推理回答,这时候我们就还需要为prompt补充一些针对具体问题的示例(in-context learning),LangChain将这一功能抽象为了Example selectors这一组件,我们可以基于关键字,相似度(通常使用MMR/cosine similarity/ngram来计算相似度, 在后面的向量数据库章节中会提到)。为了让最终的prompt不超过Language Model的token上限(各个模型的token上限见下表),LangChain还提供了LengthBasedExampleSelector,根据长度来限制example数量,对于较长的输入,它会选择包含较少示例的提示,而对于较短的输入,它会选择包含更多示例。

PromptTemplate:langchain.prompts中的PromptTemplate类使用from_template可以创建单个字符串类型的prompt,需要str.format()格式化prompt的参数。

from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template(
    "Tell me a {adjective} joke about {content}."
)
prompt_template.format(adjective="funny", content="chickens")
# Tell me a funny joke about chickens.

ChatPromptTemplate:langchain_core.prompts中的ChatPromptTemplate类,使用from_messages方法可以创建chat message类型的prompt(包含role=system/human/ai和content=str_text两部分),这样就可以以list形式,构造history对话记录,让llm进行in-context learning。需要format_messages格式化prompt的参数,得到的是AIMessage, HumanMessage, SystemMessage三者的list组合(当然我们也可以自己构建这个list,并实例化对应的Message对象)。

from langchain_core.prompts import ChatPromptTemplate
chat_template = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful AI bot. Your name is {name}."),
        ("human", "Hello, how are you doing?"),
        ("ai", "I'm doing well, thanks!"),
        ("human", "{user_input}"),
    ]
)
messages = chat_template.format_messages(name="Bob", user_input="What is your name?")
# [SystemMessage(content="You are a helpful assistant that re-writes the user's text to sound more upbeat."), HumanMessage(content="I don't like eating tasty things")]

FewShotPromptTemplate:当然更加专业的in-context learnding方式是使用FewShotPromptTemplate显示的指定Examples,构造few shot prompt。使用字典来构造examples,每个example都是一个dict,多个examples存储在一个list中。当我们给定了很多examples时,这需要使用Example Selector利用MMR/cosine similarity/ngram来计算input和examples之间的语义相似度,来决定选择哪些examples。

如下给出反义词的task(使用基于example长度的LengthBasedExampleSelector):

# 1. 定义example variables list
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
]
# 2. 为examples定义example_prompt模板
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)
# 3. 将example variables list和example_prompt输入到example_selector中
example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=25)
# 4. 定义few_shot_prompt模板(prefix + examples + suffix)
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",  # 前缀,定义task
    suffix="Input: {adjective}\nOutput:",  # 后缀
    input_variables=["adjective"],  # 后缀参数变量
)
print(dynamic_prompt.invoke({"adjective": "funny"}).text)

得到的dynamic_prompt = prefix + examples + suffix如下:

Give the antonym of every input

Input: happy
Output: sad

Input: tall
Output: short

Input: energetic
Output: lethargic

Input: sunny
Output: gloomy

Input: windy
Output: calm

Input: funny
Output:

3.2、模块 Language Model

Language Model是真正与 LLM / ChatModel 进行交互的组件,它可以直接被当作普通的openai client来使用,在LangChain中,主要使用到的是LLM,Chat Model两类Language Model

LLM: 最基础的通过“text_str in ➡️ text_str out”模式来使用的Language Model,另一方面,LangChain也收录了大量的第三方LLM。【注意:langchain v 0.2.0以后使用langchain_community导入llms模块】

from langchain.llms import vllm  # langchain v 0.1.0
from langchain_community.llms import vllm  # langchain v 0.2.0
llm = vllm.VLLM(model="/data1/huggingface/LLM/Mistral-7B-Instruct-v0.2")
llm.invoke({"input": "how can langsmith help with testing?"})

Chat Model: 另一方面,LangChain也收录了大量的第三方Chat Model,是LLM的变体,抽象了Chat这一场景下的使用模式,由“text_str in ➡️ text_str out”变成了“chat_messages in ➡️ chat_message out”。chat_message由2个组件组成:text + message type(System/Human/AI)。【注意:langchain v 0.2.0以后使用langchain_community导入chat_models模块】,3种message type如下:

  • System message - 告诉AI要做什么的背景信息上下文,用于设定llm角色。
  • Human message - 标识用户传入的消息类型
  • AI message - 标识AI返回的消息类型
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(openai_api_key="...")
from langchain_core.messages import HumanMessage, SystemMessage
messages = [
    SystemMessage(content="You're a helpful assistant"),
    HumanMessage(content="What is the purpose of model regularization?"),
]
chat.invoke(messages)

3.3、模块 Output Parsers

langchain.output_parsers中的各种解析器,通常我们希望Language Model的输出是固定的格式,以支持我们解析其输出为结构化数据,LangChain将这一诉求所需的功能抽象成了Output Parser这一组件,并提供了一系列的预定义Output Parser,如最常用的StructuredOutputParser, PydanticOutputParser,JsonOutputParser,以及在LLM输出无法解析时发挥作用的Auto-fixing parser和Retry parser等。

PydanticOutputParser:PydanticOutputParser可以使用 Pydantic 数据类作为输出格式,将Pydantic数据类导入解析器中。所有数据类都要继承的 Pydantic 的 BaseModel 就像 Python 数据类,但具有实际的类型检查 + 强制。创建prompt时要用partial_variables部分填入变量parser.get_format_instructions()。如下面的例子,生成一问一答的冷笑话(输出的数据类包含问句setup和答句punchline):

from typing import List
from langchain_openai import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field, validator

# Define your desired data structure.
class Joke(BaseModel):
    setup: str = Field(description="question to set up a joke")  # 笑话设置的部分
    punchline: str = Field(description="answer to resolve the joke")  # 笑话的冷笑话部分

    # 对笑话进行逻辑验证,保证setup的部分以?结尾
    @validator("setup")
    def question_ends_with_question_mark(cls, field):
        if field[-1] != "?":
            raise ValueError("Badly formed question!")
        return field

# 初始化ChatOpenAI实例
llm = ChatOpenAI(
    model_name='qwen1.5-32b-chat-int4',  # 替换为你使用的模型名称
    openai_api_base='http://20.20.136.251:8001/v1',  # 替换为你的API base URL
    openai_api_key='q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2'  # 替换为你的API密钥
)

# And a query intented to prompt a language model to populate the data structure.
joke_query = "Tell me a joke."

# Set up a parser + inject instructions into the prompt template.
parser = PydanticOutputParser(pydantic_object=Joke)

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(prompt)

chain = prompt | llm | parser

print(chain.invoke({"query": joke_query}))
Joke(setup="Why don't scientists trust atoms?", punchline='Because they make up everything!')

JsonOutputParser: 必须使用具有足够能力的LLM来生成格式良好的 JSON。

from typing import List

from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
model = ChatOpenAI(temperature=0)
# Define your desired data structure.
class Joke(BaseModel):
    setup: str = Field(description="question to set up a joke")
    punchline: str = Field(description="answer to resolve the joke")
    # And a query intented to prompt a language model to populate the data structure.

joke_query = "Tell me a joke."

# Set up a parser + inject instructions into the prompt template.
parser = JsonOutputParser(pydantic_object=Joke)

prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

chain = prompt | model | parser

chain.invoke({"query": joke_query})
{'setup': "Why don't scientists trust atoms?",
 'punchline': 'Because they make up everything!'}

StructuredOutputParser: 当您想要返回多个字段时,可以使用此输出解析器。虽然 Pydantic/JSON 解析器更强大,但这个解析器对于生成功能较弱的LLM模型很有用。下面例子是生成问题的答案,并给出参考的web网站url。

from langchain.output_parsers import ResponseSchema, StructuredOutputParser
from langchain_core.prompts import PromptTemplate, load_prompt
from langchain_openai import ChatOpenAI

# 初始化ChatOpenAI实例
llm = ChatOpenAI(
    model_name='qwen1.5-32b-chat-int4',  # 替换为你使用的模型名称
    openai_api_base='http://20.20.136.251:8001/v1',  # 替换为你的API base URL
    openai_api_key='q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2'  # 替换为你的API密钥
)

# 响应模式指导LLM的输出解析格式,传入StructuredOutputParser
response_schemas = [
    ResponseSchema(name="answer", description="answer to the user's question"),
    ResponseSchema(name="source", description="source used to answer the user's question, should be a website."),
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)

prompt = PromptTemplate(
    template="answer the users question as best as possible.\n{format_instructions}\n{question}",
    input_variables=["question"],
    partial_variables={"format_instructions": format_instructions},
)
print(prompt)

chain = prompt | llm | output_parser
print(chain.invoke({"question": "what's the capital of france?"}))
{'answer': 'The capital of France is Paris.',
 'source': 'https://en.wikipedia.org/wiki/Paris'}

「真诚赞赏,手留余香」

YuTian Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付