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'}
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
