大模型应用开发框架LangChain
1、Model I/O
Model I/O
是 LangChain 为开发者提供的一套面向 LLM 的标准化模型接口,包括模型输入(Prompts)、模型输出(Output Parsers)和模型本身(Models)。- Prompts:模板化、动态选择和管理模型输入。
- Models:以通用接口调用语言模型。
- Output Parser:从模型输出中提取信息,并规范化内容。
1.1 模型Model
- 模型抽象Model:
语言模型(LLMs): LangChain 的核心组件。LangChain并不提供自己的LLMs,而是为与许多不同的LLMs(OpenAI、Cohere、Hugging Face等)进行交互提供了一个标准接口。
聊天模型(Chat Models): 语言模型的一种变体。虽然聊天模型在内部使用了语言模型,但它们提供的接口略有不同。与其暴露一个“输入文本,输出文本”的API不同,它们提供了一个以“聊天消息”作为输入和输出的接口。
1.1.1 语言模型(LLMs)
类继承关系:BaseLanguageModel –> BaseLLM –> LLM –> <name> # Examples: AI21, HuggingFaceHub, OpenAI
主要抽象:LLMResult, PromptValue, CallbackManagerForLLMRun, AsyncCallbackManagerForLLMRun, CallbackManager, AsyncCallbackManager, AIMessage, BaseMessage
https://api.python.langchain.com/en/latest/langchain_api_reference.html
源码解析
BaseLanguageModel Class:这个基类为语言模型定义了一个接口,该接口允许用户以不同的方式与模型交互(例如通过提示或消息)。
generate_prompt
是其中的一个主要方法,它接受一系列提示,并返回模型的生成结果。(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/schema/language_model.py)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45# 定义 BaseLanguageModel 抽象基类,它从 Serializable, Runnable 和 ABC 继承
class BaseLanguageModel(
Serializable, Runnable[LanguageModelInput, LanguageModelOutput], ABC
):
"""
与语言模型交互的抽象基类。
所有语言模型的封装器都应从 BaseLanguageModel 继承。
主要提供三种方法:
- generate_prompt: 为一系列的提示值生成语言模型输出。提示值是可以转换为任何语言模型输入格式的模型输入(如字符串或消息)。
- predict: 将单个字符串传递给语言模型并返回字符串预测。
- predict_messages: 将一系列 BaseMessages(对应于单个模型调用)传递给语言模型,并返回 BaseMessage 预测。
每种方法都有对应的异步方法。
"""
# 定义一个抽象方法 generate_prompt,需要子类进行实现
def generate_prompt(
self,
prompts: List[PromptValue], # 输入提示的列表
stop: Optional[List[str]] = None, # 生成时的停止词列表
callbacks: Callbacks = None, # 回调,用于执行例如日志记录或流式处理的额外功能
**kwargs: Any, # 任意的额外关键字参数,通常会传递给模型提供者的 API 调用
) -> LLMResult:
"""
将一系列的提示传递给模型并返回模型的生成。
对于提供批处理 API 的模型,此方法应使用批处理调用。
使用此方法时:
1. 希望利用批处理调用,
2. 需要从模型中获取的输出不仅仅是最顶部生成的值,
3. 构建与底层语言模型类型无关的链(例如,纯文本完成模型与聊天模型)。
参数:
prompts: 提示值的列表。提示值是一个可以转换为与任何语言模型匹配的格式的对象(对于纯文本生成模型为字符串,对于聊天模型为 BaseMessages)。
stop: 生成时使用的停止词。模型输出在这些子字符串的首次出现处截断。
callbacks: 要传递的回调。用于执行额外功能,例如在生成过程中进行日志记录或流式处理。
**kwargs: 任意的额外关键字参数。通常这些会传递给模型提供者的 API 调用。
返回值:
LLMResult,它包含每个输入提示的候选生成列表以及特定于模型提供者的额外输出。
"""BaseLLM Class:这段代码定义了一个名为 BaseLLM 的抽象基类。这个基类的主要目的是提供一个基本的接口来处理大型语言模型 (LLM),使用了 Pydantic 的功能,特别是 Field 方法,用于定义默认值和序列化行为。BaseLLM 的子类需要提供实现具体功能的方法。。(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/llms/base.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34# 定义 BaseLLM 抽象基类,它从 BaseLanguageModel[str] 和 ABC(Abstract Base Class)继承
class BaseLLM(BaseLanguageModel[str], ABC):
"""Base LLM abstract interface.
It should take in a prompt and return a string."""
# 定义可选的缓存属性,其初始值为 None
cache: Optional[bool] = None
# 定义 verbose 属性,该属性决定是否打印响应文本
# 默认值使用 _get_verbosity 函数的结果
verbose: bool = Field(default_factory=_get_verbosity)
"""Whether to print out response text."""
# 定义 callbacks 属性,其初始值为 None,并从序列化中排除
callbacks: Callbacks = Field(default=None, exclude=True)
# 定义 callback_manager 属性,其初始值为 None,并从序列化中排除
callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True)
# 定义 tags 属性,这些标签会被添加到运行追踪中,其初始值为 None,并从序列化中排除
tags: Optional[List[str]] = Field(default=None, exclude=True)
"""Tags to add to the run trace."""
# 定义 metadata 属性,这些元数据会被添加到运行追踪中,其初始值为 None,并从序列化中排除
metadata: Optional[Dict[str, Any]] = Field(default=None, exclude=True)
"""Metadata to add to the run trace."""
# 内部类定义了这个 pydantic 对象的配置
class Config:
"""Configuration for this pydantic object."""
# 允许使用任意类型
arbitrary_types_allowed = TrueLLM Class:这段代码定义了一个名为 LLM 的类,该类继承自 BaseLLM。这个类的目的是为了为用户提供一个简化的接口来处理LLM(大型语言模型),而不期望用户实现完整的 _generate 方法。(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/llms/base.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44# 继承自 BaseLLM 的 LLM 类
class LLM(BaseLLM):
"""Base LLM abstract class.
The purpose of this class is to expose a simpler interface for working
with LLMs, rather than expect the user to implement the full _generate method.
"""
# 使用 @abstractmethod 装饰器定义一个抽象方法,子类需要实现这个方法
def _call(
self,
prompt: str, # 输入提示
stop: Optional[List[str]] = None, # 停止词列表
run_manager: Optional[CallbackManagerForLLMRun] = None, # 运行管理器
**kwargs: Any, # 其他关键字参数
) -> str:
"""Run the LLM on the given prompt and input."""
# 此方法的实现应在子类中提供
# _generate 方法使用了 _call 方法,用于处理多个提示
def _generate(
self,
prompts: List[str], # 多个输入提示的列表
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> LLMResult:
"""Run the LLM on the given prompt and input."""
# TODO: 在此处添加缓存逻辑
generations = [] # 用于存储生成的文本
# 检查 _call 方法的签名是否支持 run_manager 参数
new_arg_supported = inspect.signature(self._call).parameters.get("run_manager")
for prompt in prompts: # 遍历每个提示
# 根据是否支持 run_manager 参数来选择调用方法
text = (
self._call(prompt, stop=stop, run_manager=run_manager, **kwargs)
if new_arg_supported
else self._call(prompt, stop=stop, **kwargs)
)
# 将生成的文本添加到 generations 列表中
generations.append([Generation(text=text)])
# 返回 LLMResult 对象,其中包含 generations 列表
return LLMResult(generations=generations)LLMs已支持模型清单:
使用 LangChain 调用 OpenAI GPT Completion API
BaseOpenAI Class:(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/llms/openai.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57class BaseOpenAI(BaseLLM):
"""OpenAI 大语言模型的基类。"""
def lc_secrets(self) -> Dict[str, str]:
return {"openai_api_key": "OPENAI_API_KEY"}
def lc_serializable(self) -> bool:
return True
client: Any #: :meta private:
model_name: str = Field("text-davinci-003", alias="model")
"""使用的模型名。"""
temperature: float = 0.7
"""要使用的采样温度。"""
max_tokens: int = 256
"""完成中生成的最大令牌数。
-1表示根据提示和模型的最大上下文大小返回尽可能多的令牌。"""
top_p: float = 1
"""在每一步考虑的令牌的总概率质量。"""
frequency_penalty: float = 0
"""根据频率惩罚重复的令牌。"""
presence_penalty: float = 0
"""惩罚重复的令牌。"""
n: int = 1
"""为每个提示生成多少完成。"""
best_of: int = 1
"""在服务器端生成best_of完成并返回“最佳”。"""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""保存任何未明确指定的`create`调用的有效模型参数。"""
openai_api_key: Optional[str] = None
openai_api_base: Optional[str] = None
openai_organization: Optional[str] = None
# 支持OpenAI的显式代理
openai_proxy: Optional[str] = None
batch_size: int = 20
"""传递多个文档以生成时使用的批处理大小。"""
request_timeout: Optional[Union[float, Tuple[float, float]]] = None
"""向OpenAI完成API的请求超时。 默认为600秒。"""
logit_bias: Optional[Dict[str, float]] = Field(default_factory=dict)
"""调整生成特定令牌的概率。"""
max_retries: int = 6
"""生成时尝试的最大次数。"""
streaming: bool = False
"""是否流式传输结果。"""
allowed_special: Union[Literal["all"], AbstractSet[str]] = set()
"""允许的特殊令牌集。"""
disallowed_special: Union[Literal["all"], Collection[str]] = "all"
"""不允许的特殊令牌集。"""
tiktoken_model_name: Optional[str] = None
"""使用此类时传递给tiktoken的模型名。
Tiktoken用于计算文档中的令牌数量以限制它们在某个限制以下。
默认情况下,设置为None时,这将与嵌入模型名称相同。
但是,在某些情况下,您可能希望使用此嵌入类与tiktoken不支持的模型名称。
这可以包括使用Azure嵌入或使用多个模型提供商的情况,这些提供商公开了类似OpenAI的API但模型不同。
在这些情况下,为了避免在调用tiktoken时出错,您可以在此处指定要使用的模型名称。"""OpenAI LLM 模型默认使用 gpt-3.5-turbo-instruct:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from langchain_openai import OpenAI
llm = OpenAI(model_name="gpt-3.5-turbo-instruct")
print(llm.invoke("Tell me a Joke")) # Why couldn't the bicycle stand up by itself? Because it was two-tired!
print(llm.invoke("讲10个给程序员听得笑话"))
# 1. 为什么程序员总是喜欢用黑色主题?因为黑色主题更酷,而且能节省电。
# 2. 为什么程序员总是担心世界末日?因为那时候所有的代码都会变成bug。
# 3. 为什么程序员经常在夜里工作?因为他们觉得夜晚是最好的调试时间,因为所有的bug都会藏在黑暗中。
# 4. 什么是程序员最害怕的字?“404 Not Found”。
# 5. 为什么程序员总是喜欢喝咖啡?因为咖啡是唯一能让他们保持清醒的东西。
# 6. 为什么程序员总是喜欢用英文命名变量?因为他们觉得英文变量名看起来更专业,而且可以避免一些中文编码
# 生成10个笑话,显然超过了 max_token 默认值
llm.max_tokens # 256
# 修改 max_token 值为 1024
llm.max_tokens = 1024
llm.max_tokens # 1024对比直接调用 OpenAI API:
1
2
3
4
5
6
7
8from openai import OpenAI
client = OpenAI()
data = client.completions.create(
model="gpt-3.5-turbo-instruct",
prompt="Tell me a Joke",
)
1.1.2 聊天模型(Chat Models)
- 类继承关系:BaseLanguageModel –> BaseChatModel –> <name> # Examples: ChatOpenAI, ChatGooglePalm
- 主要抽象:AIMessage, BaseMessage, HumanMessage
源码解析
BaseChatModel Class:(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chat_models/base.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class BaseChatModel(BaseLanguageModel[BaseMessageChunk], ABC):
cache: Optional[bool] = None
"""是否缓存响应。"""
verbose: bool = Field(default_factory=_get_verbosity)
"""是否打印响应文本。"""
callbacks: Callbacks = Field(default=None, exclude=True)
"""添加到运行追踪的回调函数。"""
callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True)
"""添加到运行追踪的回调函数管理器。"""
tags: Optional[List[str]] = Field(default=None, exclude=True)
"""添加到运行追踪的标签。"""
metadata: Optional[Dict[str, Any]] = Field(default=None, exclude=True)
"""添加到运行追踪的元数据。"""
# 需要子类实现的 _generate 抽象方法
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:ChatOpenAI Class(调用 Chat Completion API):(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chat_models/openai.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52class ChatOpenAI(BaseChatModel):
"""OpenAI Chat大语言模型的包装器。
要使用,您应该已经安装了``openai`` python包,并且
环境变量``OPENAI_API_KEY``已使用您的API密钥进行设置。
即使未在此类上明确保存,也可以传入任何有效的参数
至openai.create调用。
"""
def lc_secrets(self) -> Dict[str, str]:
return {"openai_api_key": "OPENAI_API_KEY"}
def lc_serializable(self) -> bool:
return True
client: Any = None #: :meta private:
model_name: str = Field(default="gpt-3.5-turbo", alias="model")
"""要使用的模型名。"""
temperature: float = 0.7
"""使用的采样温度。"""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""保存任何未明确指定的`create`调用的有效模型参数。"""
openai_api_key: Optional[str] = None
"""API请求的基础URL路径,
如果不使用代理或服务仿真器,请留空。"""
openai_api_base: Optional[str] = None
openai_organization: Optional[str] = None
# 支持OpenAI的显式代理
openai_proxy: Optional[str] = None
request_timeout: Optional[Union[float, Tuple[float, float]]] = None
"""请求OpenAI完成API的超时。默认为600秒。"""
max_retries: int = 6
"""生成时尝试的最大次数。"""
streaming: bool = False
"""是否流式传输结果。"""
n: int = 1
"""为每个提示生成的聊天完成数。"""
max_tokens: Optional[int] = None
"""生成的最大令牌数。"""
tiktoken_model_name: Optional[str] = None
"""使用此类时传递给tiktoken的模型名称。
Tiktoken用于计算文档中的令牌数以限制
它们在某个限制之下。默认情况下,当设置为None时,这将
与嵌入模型名称相同。但是,在某些情况下,
您可能希望使用此嵌入类,模型名称不
由tiktoken支持。这可能包括使用Azure嵌入或
使用其中之一的多个模型提供商公开类似OpenAI的
API但模型不同。在这些情况下,为了避免在调用tiktoken时出错,
您可以在这里指定要使用的模型名称。"
使用 LangChain 调用 OpenAI GPT ChatCompletion API
案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21from langchain_openai import ChatOpenAI
chat_model = ChatOpenAI(model_name="gpt-3.5-turbo")
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
messages = [SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="Who won the world series in 2020?"),
AIMessage(content="The Los Angeles Dodgers won the World Series in 2020."),
HumanMessage(content="Where was it played?")]
print(messages) # [SystemMessage(content='You are a helpful assistant.'), HumanMessage(content='Who won the world series in 2020?'), AIMessage(content='The Los Angeles Dodgers won the World Series in 2020.'), HumanMessage(content='Where was it played?')]
chat_model.invoke(messages) # AIMessage(content='The 2020 World Series was played at Globe Life Field in Arlington, Texas.', response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 53, 'total_tokens': 70}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-17b0f35a-460a-42f4-b7c9-1b6fa2871416-0')
chat_result = chat_model.invoke(messages)
type(chat_result) # langchain_core.messages.ai.AIMessage对比调用 OpenAI API:
1
2
3
4
5
6
7
8
9
10
11import openai
data = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Who won the world series in 2020?"},
{"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
{"role": "user", "content": "Where was it played?"}
]
)
1.2 模型输入Prompts
- 一个语言模型的提示是用户提供的一组指令或输入,用于引导模型的响应,帮助它理解上下文并生成相关和连贯的基于语言的输出,例如回答问题、完成句子或进行对话。
- 提示模板(Prompt Templates):参数化的模型输入
- 示例选择器(Example Selectors):动态选择要包含在提示中的示例
1.2.1 提示模板Prompt Templates
Prompt Templates提供了一种预定义、动态注入、模型无关和参数化的提示词生成方式,以便在不同的语言模型之间重用模板。一个模板可能包括指令、少量示例以及适用于特定任务的具体背景和问题。通常,提示要么是一个字符串(LLMs),要么是一组聊天消息(Chat Model)。
类继承关系(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/prompts):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15BasePromptTemplate --> PipelinePromptTemplate
StringPromptTemplate --> PromptTemplate
FewShotPromptTemplate
FewShotPromptWithTemplates
BaseChatPromptTemplate --> AutoGPTPrompt
ChatPromptTemplate --> AgentScratchPadChatPromptTemplate
BaseMessagePromptTemplate --> MessagesPlaceholder
BaseStringMessagePromptTemplate --> ChatMessagePromptTemplate
HumanMessagePromptTemplate
AIMessagePromptTemplate
SystemMessagePromptTemplate
PromptValue --> StringPromptValue
ChatPromptValue
使用 PromptTemplate 类生成提升词
通常,
PromptTemplate
类的实例,使用Python的str.format
语法生成模板化提示;也可以使用其他模板语法(例如jinja2)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from langchain import PromptTemplate
# 使用 from_template 方法实例化 PromptTemplate
prompt_template = PromptTemplate.from_template(
"Tell me a {adjective} joke about {content}."
)
# 使用 format 生成提示
prompt = prompt_template.format(adjective="funny", content="chickens")
print(prompt) # Tell me a funny joke about chickens.
print(prompt_template) # input_variables=['adjective', 'content'] input_types={} partial_variables={} template='Tell me a {adjective} joke about {content}.'
prompt_template = PromptTemplate.from_template(
"Tell me a joke"
)
# 生成提示
prompt = prompt_template.format()
print(prompt) # Tell me a joke
使用构造函数(Initializer)实例化 PromptTemplate
使用构造函数实例化
prompt_template
时必须传入参数:input_variables
和template
。在生成提示过程中,会检查输入变量与模板字符串中的变量是否匹配,如果不匹配,则会引发异常。1
2
3
4invalid_prompt = PromptTemplate(
input_variables=["adjective"],
template="Tell me a {adjective} joke about {content}."
)传入 content 后才能生成可用的 prompt:
1
2
3
4
5
6
7valid_prompt = PromptTemplate(
input_variables=["adjective", "content"],
template="Tell me a {adjective} joke about {content}."
)
print(valid_prompt) # input_variables=['adjective', 'content'] input_types={} partial_variables={} template='Tell me a {adjective} joke about {content}.'
valid_prompt.format(adjective="funny", content="chickens") # 'Tell me a funny joke about chickens.'1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from langchain_openai import OpenAI
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", max_tokens=1000)
prompt_template = PromptTemplate.from_template(
"讲{num}个给程序员听得笑话"
)
prompt = prompt_template.format(num=2)
print(f"prompt: {prompt}") # prompt: 讲2个给程序员听得笑话
result = llm.invoke(prompt)
print(f"result: {result}") # result: content='当然可以!给程序员的笑话如下:\n\n1. 为什么程序员总是喜欢在洗手间里工作?\n 因为那里有很多“控制”流(control flow)!\n\n2. 有一天,一个程序员走进了酒吧,点了一杯啤酒。酒保问他:“要不要加点柠檬?”程序员回答:“不,我只想要最简单的‘Hello, World!’”\n\n希望这些笑话能让你会心一笑!' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 106, 'prompt_tokens': 17, 'total_tokens': 123, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': None}}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_b705f0c291', 'finish_reason': 'stop', 'logprobs': None} id='run-3231f13c-95dc-4e93-ba30-3d5afdd03560-0' usage_metadata={'input_tokens': 17, 'output_tokens': 106, 'total_tokens': 123, 'input_token_details': {}, 'output_token_details': {}}
print(llm.invoke(prompt_template.format(num=3))) # content='当然可以!以下是三个适合程序员的笑话:\n\n1. **为什么程序员总是混淆万圣节和圣诞节?**\n 因为Oct 31 等于 Dec 25!\n\n2. **程序员的妻子对他生气了,问他为什么总是对代码那么上心。**\n 程序员回答:“因为它们比我们之间的沟通更容易调试!”\n\n3. **有什么是程序员最不喜欢的饮料?**\n 可乐,因为他们讨厌“糖”(bug)!\n\n希望你喜欢这些笑话!' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 131, 'prompt_tokens': 17, 'total_tokens': 148, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': None}}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_b705f0c291', 'finish_reason': 'stop', 'logprobs': None} id='run-2f34ac13-aadf-451a-ad5e-942a36737161-0' usage_metadata={'input_tokens': 17, 'output_tokens': 131, 'total_tokens': 148, 'input_token_details': {}, 'output_token_details': {}}
# 【新增】ChatOpenAI调用方式,使用gpt-4o-mini模型
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model_name="gpt-4o-mini", max_tokens=1000)
prompt = prompt_template.format(num=2)
print(f"prompt: {prompt}")
result = llm.invoke(prompt)
print(f"result: {result}")1
2
3
4
5# 使用 jinja2 生成模板化提示
jinja2_template = "Tell me a {{ adjective }} joke about {{ content }}"
prompt = PromptTemplate.from_template(jinja2_template, template_format="jinja2")
prompt.format(adjective="funny", content="chickens") # 'Tell me a funny joke about chickens'
print(prompt) # input_variables=['adjective', 'content'] input_types={} partial_variables={} template='Tell me a {{ adjective }} joke about {{ content }}' template_format='jinja2'
实测:生成多种编程语言版本的快速排序
1 | sort_prompt_template = PromptTemplate.from_template( |
1.2.2 聊天模版ChatPromptTemplate
ChatPromptTemplate
类的实例,使用format_messages
方法生成适用于聊天模型的提示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 使用 from_messages 方法实例化 ChatPromptTemplate
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 = template.format_messages(
name="Bob",
user_input="What is your name?"
)
print(messages) # [SystemMessage(content='You are a helpful AI bot. Your name is Bob.', additional_kwargs={}, response_metadata={}), HumanMessage(content='Hello, how are you doing?', additional_kwargs={}, response_metadata={}), AIMessage(content="I'm doing well, thanks!", additional_kwargs={}, response_metadata={}), HumanMessage(content='What is your name?', additional_kwargs={}, response_metadata={})]
print(messages[0].content) # You are a helpful AI bot. Your name is Bob.
print(messages[-1].content) # What is your name?
chat_model = ChatOpenAI(model_name="gpt-3.5-turbo", max_tokens=1000)
chat_model.invoke(messages) # AIMessage(content='My name is Bob.\n', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 6, 'prompt_tokens': 51, 'total_tokens': 57, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': 0, 'reasoning_tokens': None, 'rejected_prediction_tokens': None, 'text_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0, 'text_tokens': 0, 'image_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': '', 'finish_reason': 'stop', 'logprobs': None}, id='run-56b4bc27-bb95-4687-87e1-001af8ff6f84-0', usage_metadata={'input_tokens': 51, 'output_tokens': 6, 'total_tokens': 57, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0}})
摘要总结案例
1 | from langchain_openai import ChatOpenAI |
1.2.3 示例模版FewShotPromptTemplate
构造 few-shot prompt 的方法通常有两种:
- 从示例集(set of examples)中手动选择;
- 通过示例选择器(Example Selector)自动选择。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88from langchain.prompts.prompt import PromptTemplate
examples = [
{
"question": "谁活得更久,穆罕默德·阿里还是艾伦·图灵?",
"answer":
"""
这里需要进一步的问题吗:是的。
追问:穆罕默德·阿里去世时多大了?
中间答案:穆罕默德·阿里去世时74岁。
追问:艾伦·图灵去世时多大了?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:穆罕默德·阿里
"""
},
{
"question": "craigslist的创始人是什么时候出生的?",
"answer":
"""
这里需要进一步的问题吗:是的。
追问:谁是craigslist的创始人?
中间答案:Craigslist是由Craig Newmark创办的。
追问:Craig Newmark是什么时候出生的?
中间答案:Craig Newmark出生于1952年12月6日。
所以最终答案是:1952年12月6日
"""
},
{
"question": "乔治·华盛顿的外祖父是谁?",
"answer":
"""
这里需要进一步的问题吗:是的。
追问:谁是乔治·华盛顿的母亲?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
追问:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
"""
},
{
"question": "《大白鲨》和《皇家赌场》的导演是同一个国家的吗?",
"answer":
"""
这里需要进一步的问题吗:是的。
追问:谁是《大白鲨》的导演?
中间答案:《大白鲨》的导演是Steven Spielberg。
追问:Steven Spielberg来自哪里?
中间答案:美国。
追问:谁是《皇家赌场》的导演?
中间答案:《皇家赌场》的导演是Martin Campbell。
追问:Martin Campbell来自哪里?
中间答案:新西兰。
所以最终答案是:不是
"""
}
]
example_prompt = PromptTemplate(
input_variables=["question", "answer"],
template="Question: {question}\n{answer}"
)
# **examples[0] 是将examples[0] 字典的键值对(question-answer)解包并传递给format,作为函数参数
print(example_prompt.format(**examples[0]))
# Question: 谁活得更久,穆罕默德·阿里还是艾伦·图灵?
#
# 这里需要进一步的问题吗:是的。
# 追问:穆罕默德·阿里去世时多大了?
# 中间答案:穆罕默德·阿里去世时74岁。
# 追问:艾伦·图灵去世时多大了?
# 中间答案:艾伦·图灵去世时41岁。
# 所以最终答案是:穆罕默德·阿里
print(example_prompt) # input_variables=['answer', 'question'] input_types={} partial_variables={} template='Question: {question}\n{answer}'
print(example_prompt.format(**examples[-1]))
# Question: 《大白鲨》和《皇家赌场》的导演是同一个国家的吗?
#
# 这里需要进一步的问题吗:是的。
# 追问:谁是《大白鲨》的导演?
# 中间答案:《大白鲨》的导演是Steven Spielberg。
# 追问:Steven Spielberg来自哪里?
# 中间答案:美国。
# 追问:谁是《皇家赌场》的导演?
# 中间答案:《皇家赌场》的导演是Martin Campbell。
# 追问:Martin Campbell来自哪里?
# 中间答案:新西兰。
# 所以最终答案是:不是生成 Few-shot Prompt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58# 导入 FewShotPromptTemplate 类
from langchain.prompts.few_shot import FewShotPromptTemplate
# 创建一个 FewShotPromptTemplate 对象
few_shot_prompt = FewShotPromptTemplate(
examples=examples, # 使用前面定义的 examples 作为范例
example_prompt=example_prompt, # 使用前面定义的 example_prompt 作为提示模板
suffix="Question: {input}", # 后缀模板,其中 {input} 会被替换为实际输入
input_variables=["input"] # 定义输入变量的列表
)
# 使用给定的输入格式化 prompt,并打印结果
# 这里的 {input} 将被 "玛丽·波尔·华盛顿的父亲是谁?" 替换
print(few_shot_prompt.format(input="玛丽·波尔·华盛顿的父亲是谁?"))
# Question: 谁活得更久,穆罕默德·阿里还是艾伦·图灵?
#
# 这里需要进一步的问题吗:是的。
# 追问:穆罕默德·阿里去世时多大了?
# 中间答案:穆罕默德·阿里去世时74岁。
# 追问:艾伦·图灵去世时多大了?
# 中间答案:艾伦·图灵去世时41岁。
# 所以最终答案是:穆罕默德·阿里
#
#
# Question: craigslist的创始人是什么时候出生的?
#
# 这里需要进一步的问题吗:是的。
# 追问:谁是craigslist的创始人?
# 中间答案:Craigslist是由Craig Newmark创办的。
# 追问:Craig Newmark是什么时候出生的?
# 中间答案:Craig Newmark出生于1952年12月6日。
# 所以最终答案是:1952年12月6日
#
#
# Question: 乔治·华盛顿的外祖父是谁?
#
# 这里需要进一步的问题吗:是的。
# 追问:谁是乔治·华盛顿的母亲?
# 中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
# 追问:Mary Ball Washington的父亲是谁?
# 中间答案:Mary Ball Washington的父亲是Joseph Ball。
# 所以最终答案是:Joseph Ball
#
#
# Question: 《大白鲨》和《皇家赌场》的导演是同一个国家的吗?
#
# 这里需要进一步的问题吗:是的。
# 追问:谁是《大白鲨》的导演?
# 中间答案:《大白鲨》的导演是Steven Spielberg。
# 追问:Steven Spielberg来自哪里?
# 中间答案:美国。
# 追问:谁是《皇家赌场》的导演?
# 中间答案:《皇家赌场》的导演是Martin Campbell。
# 追问:Martin Campbell来自哪里?
# 中间答案:新西兰。
# 所以最终答案是:不是
#
# Question: 玛丽·波尔·华盛顿的父亲是谁?示例选择器 Example Selectors:如果你有大量的参考示例,就得选择哪些要包含在提示中。最好还是根据某种条件或者规则来自动选择,Example Selector 是负责这个任务的类。
BaseExampleSelector 定义如下:
1
2
3
4
5
6class BaseExampleSelector(ABC):
"""用于选择包含在提示中的示例的接口。"""
def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
"""根据输入选择要使用的示例。"""ABC
是 Python 中的abc
模块中的一个缩写,它表示 “Abstract Base Class”(抽象基类)。在 Python 中,抽象基类用于定义其他类必须遵循的基本接口或蓝图,但不能直接实例化。其主要目的是为了提供一种形式化的方式来定义和检查子类的接口。使用抽象基类的几点关键信息:- 抽象方法:在抽象基类中,你可以定义抽象方法,它没有实现(也就是说,它没有方法体)。任何继承该抽象基类的子类都必须提供这些抽象方法的实现。
- 不能直接实例化:你不能直接创建抽象基类的实例。试图这样做会引发错误。它们的主要目的是为了被继承,并在子类中实现其方法。
- 强制子类实现:如果子类没有实现所有的抽象方法,那么试图实例化该子类也会引发错误。这确保了继承抽象基类的所有子类都遵循了预定的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67# 导入需要的模块和类
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate
# 定义一个提示模板
example_prompt = PromptTemplate(
input_variables=["input", "output"], # 输入变量的名字
template="Input: {input}\nOutput: {output}", # 实际的模板字符串
)
# 这是一个假设的任务示例列表,用于创建反义词
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
{"input": "sunny", "output": "gloomy"},
{"input": "windy", "output": "calm"},
]
# 从给定的示例中创建一个语义相似性选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples, # 可供选择的示例列表
OpenAIEmbeddings(), # 用于生成嵌入向量的嵌入类,用于衡量语义相似性
Chroma, # 用于存储嵌入向量并进行相似性搜索的 VectorStore 类
k=1 # 要生成的示例数量
)
# 创建一个 FewShotPromptTemplate 对象
similar_prompt = FewShotPromptTemplate(
example_selector=example_selector, # 提供一个 ExampleSelector 替代示例
example_prompt=example_prompt, # 前面定义的提示模板
prefix="Give the antonym of every input", # 前缀模板
suffix="Input: {adjective}\nOutput:", # 后缀模板
input_variables=["adjective"], # 输入变量的名字
)
# 输入是一种感受,所以应该选择 happy/sad 的示例。
print(similar_prompt.format(adjective="worried"))
# Give the antonym of every input
#
# Input: happy
# Output: sad
#
# Input: worried
# Output:
# 输入是一种度量,所以应该选择 tall/short的示例。
print(similar_prompt.format(adjective="long"))
# Give the antonym of every input
#
# Input: tall
# Output: short
#
# Input: long
# Output:
print(similar_prompt.format(adjective="rain"))
# Give the antonym of every input
#
# Input: windy
# Output: calm
#
# Input: rain
# Output:
1.3 模型输出Output Parser
语言模型的输出是文本。但很多时候,您可能希望获得比纯文本更结构化的信息。这就是输出解析器的价值所在。输出解析器是帮助结构化语言模型响应的类。它们必须实现两种主要方法:
“获取格式指令”:返回一个包含有关如何格式化语言模型输出的字符串的方法。
“解析”:接受一个字符串(假设为来自语言模型的响应),并将其解析成某种结构。
然后还有一种可选方法:
- “使用提示进行解析”:接受一个字符串(假设为来自语言模型的响应)和一个提示(假设为生成此响应的提示),并将其解析成某种结构。在需要重新尝试或修复输出,并且需要从提示中获取信息以执行此操作时,通常会提供提示。
1.3.1 列表解析List Parser
当您想要返回一个逗号分隔的项目列表时,可以使用此输出解析器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_openai import OpenAI
# 创建一个输出解析器,用于处理带逗号分隔的列表输出
output_parser = CommaSeparatedListOutputParser()
# 获取格式化指令,该指令告诉模型如何格式化其输出
format_instructions = output_parser.get_format_instructions()
# 创建一个提示模板,它会基于给定的模板和变量来生成提示
prompt = PromptTemplate(
template="List five {subject}.\n{format_instructions}", # 模板内容
input_variables=["subject"], # 输入变量
partial_variables={"format_instructions": format_instructions} # 预定义的变量,这里我们传入格式化指令
)
# 使用提示模板和给定的主题来格式化输入
_input = prompt.format(subject="ice cream flavors")
print(_input)
# List five ice cream flavors.
# Your response should be a list of comma separated values, eg: `foo, bar, baz` or `foo,bar,baz`
llm = OpenAI(temperature=0)
output = llm.invoke(_input)
print(output)
# 1. Chocolate
# 2. Vanilla
# 3. Strawberry
# 4. Mint chocolate chip
# 5. Cookies and cream
# 使用之前创建的输出解析器来解析模型的输出
output_parser.parse(output) # ['1. Chocolate\n2. Vanilla\n3. Strawberry\n4. Mint chocolate chip\n5. Cookies and cream']
1.3.2 日期解析Datatime Parser
1 | from langchain.output_parsers import DatetimeOutputParser |
2、Chains
对于简单的大模型应用,单独使用语言模型(LLMs)是可以的。
但更复杂的大模型应用需要将
LLMs
和Chat Models
链接在一起 - 要么彼此链接,要么与其他组件链接。- LangChain 为这种“链式”应用程序提供了
Chain
接口。 - LangChain 以通用方式定义了
Chain
,它是对组件进行调用序列的集合,其中可以包含其他链。
- LangChain 为这种“链式”应用程序提供了
Chain Class 基类(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/base.py):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52# 类继承关系:Chain --> <name>Chain # Examples: LLMChain, MapReduceChain, RouterChain
# 定义一个名为Chain的基础类
class Chain(Serializable, Runnable[Dict[str, Any], Dict[str, Any]], ABC):
"""为创建结构化的组件调用序列的抽象基类。
链应该用来编码对组件的一系列调用,如模型、文档检索器、其他链等,并为此序列提供一个简单的接口。
Chain接口使创建应用程序变得容易,这些应用程序是:
- 有状态的:给任何Chain添加Memory可以使它具有状态,
- 可观察的:向Chain传递Callbacks来执行额外的功能,如记录,这在主要的组件调用序列之外,
- 可组合的:Chain API足够灵活,可以轻松地将Chains与其他组件结合起来,包括其他Chains。
链公开的主要方法是:
- `__call__`:链是可以调用的。`__call__`方法是执行Chain的主要方式。它将输入作为一个字典接收,并返回一个字典输出。
- `run`:一个方便的方法,它以args/kwargs的形式接收输入,并将输出作为字符串或对象返回。这种方法只能用于一部分链,不能像`__call__`那样返回丰富的输出。
"""
# 调用链
def invoke(
self, input: Dict[str, Any], config: Optional[runnableConfig] = None
) -> Dict[str, Any]:
"""传统调用方法。"""
return self(input, **(config or {}))
# 链的记忆,保存状态和变量
memory: Optional[BaseMemory] = None
"""可选的内存对象,默认为None。
内存是一个在每个链的开始和结束时被调用的类。在开始时,内存加载变量并在链中传递它们。在结束时,它保存任何返回的变量。
有许多不同类型的内存,请查看内存文档以获取完整的目录。"""
# 回调,可能用于链的某些操作或事件。
callbacks: Callbacks = Field(default=None, exclude=True)
"""可选的回调处理程序列表(或回调管理器)。默认为None。
在对链的调用的生命周期中,从on_chain_start开始,到on_chain_end或on_chain_error结束,都会调用回调处理程序。
每个自定义链可以选择调用额外的回调方法,详细信息请参见Callback文档。"""
# 是否详细输出模式
verbose: bool = Field(default_factory=_get_verbosity)
"""是否以详细模式运行。在详细模式下,一些中间日志将打印到控制台。默认值为`langchain.verbose`。"""
# 与链关联的标签
tags: Optional[List[str]] = None
"""与链关联的可选标签列表,默认为None。
这些标签将与对这个链的每次调用关联起来,并作为参数传递给在`callbacks`中定义的处理程序。
你可以使用这些来例如识别链的特定实例与其用例。"""
# 与链关联的元数据
metadata: Optional[Dict[str, Any]] = None
"""与链关联的可选元数据,默认为None。
这些元数据将与对这个链的每次调用关联起来,并作为参数传递给在`callbacks`中定义的处理程序。
你可以使用这些来例如识别链的特定实例与其用例。"""
2.1 LLMChain
LLMChain 是 LangChain 中最简单的链,作为其他复杂 Chains 和 Agents 的内部调用,被广泛应用。
一个LLMChain由PromptTemplate和语言模型(LLM or Chat Model)组成。它使用直接传入(或 memory 提供)的 key-value 来规范化生成 Prompt Template(提示模板),并将生成的 prompt (格式化后的字符串)传递给大模型,并返回大模型输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# from langchain_openai import OpenAI
# from langchain.prompts import PromptTemplate
# llm = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0.9, max_tokens=500)
# 【新增】ChatOpenAI调用方式,使用gpt-4o-mini模型
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0.9, max_tokens=500)
prompt = PromptTemplate(
input_variables=["product"],
template="给制造{product}的有限公司取10个好名字,并给出完整的公司名称",
)
from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt)
print(chain.invoke({
'product': "性能卓越的GPU"
}))
# {'product': '性能卓越的GPU', 'text': '以下是十个适合制造性能卓越的GPU公司的名称及其完整的公司名称:\n\n1. **极限视觉科技有限公司** \n (Limitless Vision Technology Co., Ltd.)\n\n2. **超频图形处理有限公司** \n (Overclock Graphics Processing Co., Ltd.)\n\n3. **锐视电子科技有限公司** \n (SharpSight Electronics Technology Co., Ltd.)\n\n4. **未来显卡制造有限公司** \n (Future GPU Manufacturing Co., Ltd.)\n\n5. **动能图形解决方案有限公司** \n (Kinetic Graphics Solutions Co., Ltd.)\n\n6. **超性能图形科技有限公司** \n (Ultra Performance Graphics Technology Co., Ltd.)\n\n7. **高效计算视觉有限公司** \n (Efficient Computing Vision Co., Ltd.)\n\n8. **极致图形创新有限公司** \n (Ultimate Graphics Innovations Co., Ltd.)\n\n9. **智绘科技有限公司** \n (SmartRender Technology Co., Ltd.)\n\n10. **顶尖图形硬件有限公司** \n (TopTier Graphics Hardware Co., Ltd.)\n\n希望这些名称能够激发您的灵感!'}
chain.verbose =True
print(chain.invoke({
'product': "性能卓越的GPU"
}))
The class
LLMChain
was deprecated in LangChain 0.1.17 and will be removed in 1.0. Use :meth:~RunnableSequence, e.g.,
prompt | llm`` instead.【新增】langchain 0.3版本,使用RunnableSequence替换LLMChain,并指定 output_key
2.2 Sequential Chain
串联式调用语言模型(将一个调用的输出作为另一个调用的输入)。
顺序链(Sequential Chain )允许用户连接多个链并将它们组合成执行特定场景的流水线(Pipeline)。有两种类型的顺序链:
SimpleSequentialChain:最简单形式的顺序链,每个步骤都具有单一输入/输出,并且一个步骤的输出是下一个步骤的输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# 使用 SimpleSequentialChain 实现戏剧摘要和评论(单输入/单输出)
# 这是一个 LLMChain,用于根据剧目的标题撰写简介。
# llm = OpenAI(temperature=0.7, max_tokens=1000)
# 【新增】ChatOpenAI调用方式,使用gpt-4o-mini模型
llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0.9, max_tokens=500)
template = """你是一位剧作家。根据戏剧的标题,你的任务是为该标题写一个简介。
标题:{title}
剧作家:以下是对上述戏剧的简介:"""
prompt_template = PromptTemplate(input_variables=["title"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)
# 这是一个LLMChain,用于根据剧情简介撰写一篇戏剧评论。
# llm = OpenAI(temperature=0.7, max_tokens=1000)
template = """你是《纽约时报》的戏剧评论家。根据剧情简介,你的工作是为该剧撰写一篇评论。
剧情简介:
{synopsis}
以下是来自《纽约时报》戏剧评论家对上述剧目的评论:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template)1
2
3
4
5# 这是一个SimpleSequentialChain,按顺序运行这两个链
from langchain.chains import SimpleSequentialChain
overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)
review = overall_chain.invoke("三体人不是无法战胜的")1
review = overall_chain.invoke("星球大战第九季")
SequentialChain:更通用形式的顺序链,允许多个输入/输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# 使用 SequentialChain 实现戏剧摘要和评论(多输入/多输出)
# # 这是一个 LLMChain,根据剧名和设定的时代来撰写剧情简介。
# llm = OpenAI(temperature=.7, max_tokens=1000)
# # 【新增】ChatOpenAI调用方式,使用gpt-4o-mini模型
llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0.9, max_tokens=500)
template = """你是一位剧作家。根据戏剧的标题和设定的时代,你的任务是为该标题写一个简介。
标题:{title}
时代:{era}
剧作家:以下是对上述戏剧的简介:"""
prompt_template = PromptTemplate(input_variables=["title", "era"], template=template)
# output_key
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="synopsis", verbose=True)
# 这是一个LLMChain,用于根据剧情简介撰写一篇戏剧评论。
template = """你是《纽约时报》的戏剧评论家。根据该剧的剧情简介,你需要撰写一篇关于该剧的评论。
剧情简介:
{synopsis}
来自《纽约时报》戏剧评论家对上述剧目的评价:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review", verbose=True)1
2
3
4
5
6
7
8
9from langchain.chains import SequentialChain
m_overall_chain = SequentialChain(
chains=[synopsis_chain, review_chain],
input_variables=["era", "title"],
# Here we return multiple variables
output_variables=["synopsis", "review"],
verbose=True)
m_overall_chain.invoke({"title":"三体人不是无法战胜的", "era": "二十一世纪的新中国"})
2.3 Transform Chain
Transform Chain可实现快捷处理超长文本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate
from langchain_core.runnables import RunnableSequence
with open("../tests/the_old_man_and_the_sea.txt", encoding='utf-8') as f:
novel_text = f.read()
print(novel_text)
len(novel_text) # 47867
# 定义一个转换函数,输入是一个字典,输出也是一个字典。
def transform_func(inputs: dict) -> dict:
# 从输入字典中获取"text"键对应的文本。
text = inputs["text"]
# 使用split方法将文本按照"\n\n"分隔为多个段落,并只取前三个,然后再使用"\n\n"将其连接起来。
shortened_text = "\n\n".join(text.split("\n\n")[:3])
# 返回裁剪后的文本,用"output_text"作为键。
return {"output_text": shortened_text}
# 使用上述转换函数创建一个TransformChain对象。
# 定义输入变量为["text"],输出变量为["output_text"],并指定转换函数为transform_func。
transform_chain = TransformChain(
input_variables=["text"], output_variables=["output_text"], transform=transform_func
)
transformed_novel = transform_chain.invoke(novel_text)
print(transformed_novel['text'])1
2
3
4
5
6
7
8template = """总结下面文本:
{output_text}
总结:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt, verbose=True)
llm_chain.invoke(transformed_novel['output_text'][:1000])1
2
3# 使用 Sequential Chain 编排
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
sequential_chain.invoke(novel_text[:100])
2.4 Router Chain
Router Chain可实现条件判断的大模型调用。
这段代码构建了一个可定制的链路系统,用户可以提供不同的输入提示,并根据这些提示获取适当的响应。
主要逻辑:从
prompt_infos
创建多个LLMChain
对象,并将它们保存在一个字典中,然后创建一个默认的ConversationChain
,最后创建一个带有路由功能的MultiPromptChain
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52from langchain.chains.router import MultiPromptChain
from langchain_openai import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
physics_template = """你是一位非常聪明的物理教授。
你擅长以简洁易懂的方式回答关于物理的问题。
当你不知道某个问题的答案时,你会坦诚承认。
这是一个问题:
{input}"""
math_template = """你是一位很棒的数学家。你擅长回答数学问题。
之所以如此出色,是因为你能够将难题分解成各个组成部分,
先回答这些组成部分,然后再将它们整合起来回答更广泛的问题。
这是一个问题:
{input}"""
prompt_infos = [
{
"name": "物理",
"description": "适用于回答物理问题",
"prompt_template": physics_template,
},
{
"name": "数学",
"description": "适用于回答数学问题",
"prompt_template": math_template,
},
]
llm = OpenAI(model_name="gpt-3.5-turbo-instruct")
# 创建一个空的目标链字典,用于存放根据prompt_infos生成的LLMChain。
destination_chains = {}
# 遍历prompt_infos列表,为每个信息创建一个LLMChain。
for p_info in prompt_infos:
name = p_info["name"] # 提取名称
prompt_template = p_info["prompt_template"] # 提取模板
# 创建PromptTemplate对象
prompt = PromptTemplate(template=prompt_template, input_variables=["input"])
# 使用上述模板和llm对象创建LLMChain对象
chain = LLMChain(llm=llm, prompt=prompt)
# 将新创建的chain对象添加到destination_chains字典中
destination_chains[name] = chain
# 创建一个默认的ConversationChain
default_chain = ConversationChain(llm=llm, output_key="text")
type(default_chain) # langchain.chains.conversation.base.ConversationChain使用 LLMRouterChain 实现条件判断调用:这段代码定义了一个chain对象(LLMRouterChain),该对象首先使用router_chain来决定哪个destination_chain应该被执行,如果没有合适的目标链,则默认使用default_chain。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
# 从prompt_infos中提取目标信息并将其转化为字符串列表
destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos]
# 使用join方法将列表转化为字符串,每个元素之间用换行符分隔
destinations_str = "\n".join(destinations)
# 根据MULTI_PROMPT_ROUTER_TEMPLATE格式化字符串和destinations_str创建路由模板
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str)
# 创建路由的PromptTemplate
router_prompt = PromptTemplate(
template=router_template,
input_variables=["input"],
output_parser=RouterOutputParser(),
)
# 使用上述路由模板和llm对象创建LLMRouterChain对象
router_chain = LLMRouterChain.from_llm(llm, router_prompt)
print(destinations) # ['物理: 适用于回答物理问题', '数学: 适用于回答数学问题']
print(destinations_str)
# 物理: 适用于回答物理问题
# 数学: 适用于回答数学问题
print(MULTI_PROMPT_ROUTER_TEMPLATE)
# Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for. You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.
#
# << FORMATTING >>
# Return a markdown code snippet with a JSON object formatted to look like:
# ```json
# {{{{
# "destination": string \ name of the prompt to use or "DEFAULT"
# "next_inputs": string \ a potentially modified version of the original input
# }}}}
# ```
#
# REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts.
# REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.
#
# << CANDIDATE PROMPTS >>
# {destinations}
#
# << INPUT >>
# {{input}}
#
# << OUTPUT (must include ```json at the start of the response) >>
# << OUTPUT (must end with ```) >>
print(router_template)
# Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for. You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.
#
# << FORMATTING >>
# Return a markdown code snippet with a JSON object formatted to look like:
# ```json
# {{
# "destination": string \ name of the prompt to use or "DEFAULT"
# "next_inputs": string \ a potentially modified version of the original input
# }}
# ```
#
# REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts.
# REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.
#
# << CANDIDATE PROMPTS >>
# 物理: 适用于回答物理问题
# 数学: 适用于回答数学问题
#
# << INPUT >>
# {input}
#
# << OUTPUT (must include ```json at the start of the response) >>
# << OUTPUT (must end with ```) >>
# 创建MultiPromptChain对象,其中包含了路由链,目标链和默认链。
chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=destination_chains,
default_chain=default_chain,
verbose=True,
)
print(chain.invoke("黑体辐射是什么??"))1
2router_chain.verbose = True
print(chain.invoke("黑洞是什么?"))
3、Memory
大多数LLM应用都具有对话界面。对话的一个重要组成部分是能够引用先前在对话中介绍过的信息。至少,一个对话系统应该能够直接访问一些过去消息的窗口。更复杂的系统将需要拥有一个不断更新的世界模型,使其能够保持关于实体及其关系的信息。
我们将存储过去交互信息的能力称为“记忆(Memory)”。
LangChain提供了许多用于向应用/系统中添加 Memory 的实用工具。这些工具可以单独使用,也可以无缝地集成到链中。
一个记忆系统(Memory System)需要支持两个基本操作:读取(READ)和写入(WRITE)。
每个链都定义了一些核心执行逻辑,并期望某些输入。其中一些输入直接来自用户,但有些输入可能来自 Memory。
在一个典型 Chain 的单次运行中,将与其 Memory System 进行至少两次交互:
- 在接收到初始用户输入之后,在执行核心逻辑之前,链将从其 Memory 中读取并扩充用户输入。
- 在执行核心逻辑之后但在返回答案之前,一个链条将把当前运行的输入和输出写入 Memory ,以便在未来的运行中可以引用它们。
BaseMemory Class基类(代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/schema/memory.py):
- 类继承关系:BaseMemory –> BaseChatMemory –> <name>Memory # Examples: ZepMemory, MotorheadMemory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45# 定义一个名为BaseMemory的基础类
class BaseMemory(Serializable, ABC):
"""用于Chains中的内存的抽象基类。
这里的内存指的是Chains中的状态。内存可以用来存储关于Chain的过去执行的信息,
并将该信息注入到Chain的未来执行的输入中。例如,对于会话型Chains,内存可以用来
存储会话,并自动将它们添加到未来的模型提示中,以便模型具有必要的上下文来连贯地
响应最新的输入。"""
# 定义一个名为Config的子类
class Config:
"""为此pydantic对象配置。
Pydantic是一个Python库,用于数据验证和设置管理,主要基于Python类型提示。
"""
# 允许在pydantic模型中使用任意类型。这通常用于允许复杂的数据类型。
arbitrary_types_allowed = True
# 下面是一些必须由子类实现的方法:
# 定义一个属性,它是一个抽象方法。任何从BaseMemory派生的子类都需要实现此方法。
# 此方法应返回该内存类将添加到链输入的字符串键。
def memory_variables(self) -> List[str]:
"""获取此内存类将添加到链输入的字符串键。"""
# 定义一个抽象方法。任何从BaseMemory派生的子类都需要实现此方法。
# 此方法基于给定的链输入返回键值对。
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
"""根据给链的文本输入返回键值对。"""
# 定义一个抽象方法。任何从BaseMemory派生的子类都需要实现此方法。
# 此方法将此链运行的上下文保存到内存。
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
"""保存此链运行的上下文到内存。"""
# 定义一个抽象方法。任何从BaseMemory派生的子类都需要实现此方法。
# 此方法清除内存内容。
def clear(self) -> None:
"""清除内存内容。"""BaseChatMessageHistory Class基类:
- 类继承关系:BaseChatMessageHistory –> <name>ChatMessageHistory # Example: ZepChatMessageHistory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39# 定义一个名为BaseChatMessageHistory的基础类
class BaseChatMessageHistory(ABC):
"""聊天消息历史记录的抽象基类。"""
# 在内存中存储的消息列表
messages: List[BaseMessage]
# 定义一个add_user_message方法,它是一个方便的方法,用于将人类消息字符串添加到存储区。
def add_user_message(self, message: str) -> None:
"""为存储添加一个人类消息字符串的便捷方法。
参数:
message: 人类消息的字符串内容。
"""
self.add_message(HumanMessage(content=message))
# 定义一个add_ai_message方法,它是一个方便的方法,用于将AI消息字符串添加到存储区。
def add_ai_message(self, message: str) -> None:
"""为存储添加一个AI消息字符串的便捷方法。
参数:
message: AI消息的字符串内容。
"""
self.add_message(AIMessage(content=message))
# 抽象方法,需要由继承此基类的子类来实现。
def add_message(self, message: BaseMessage) -> None:
"""将Message对象添加到存储区。
参数:
message: 要存储的BaseMessage对象。
"""
raise NotImplementedError()
# 抽象方法,需要由继承此基类的子类来实现。
def clear(self) -> None:
"""从存储中删除所有消息"""
3.1 ConversationBufferMemory
ConversationBufferMemory
可以用来存储消息,并将消息提取到一个变量中。1
2
3
4
5
6
7
8
9
10
11
12from langchain_openai import OpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
llm = OpenAI(temperature=0)
conversation = ConversationChain(
llm=llm,
verbose=True,
memory=ConversationBufferMemory()
)
conversation.predict(input="你好呀!")1
conversation.predict(input="你为什么叫小米?跟雷军有关系吗?")
3.2 ConversationBufferWindowMemory
ConversationBufferWindowMemory
会在时间轴上保留对话的交互列表。它只使用最后 K 次交互。这对于保持最近交互的滑动窗口非常有用,以避免缓冲区过大。1
2
3
4
5
6
7
8
9from langchain.memory import ConversationBufferWindowMemory
conversation_with_summary = ConversationChain(
llm=OpenAI(temperature=0, max_tokens=1000),
# We set a low k=2, to only keep the last 2 interactions in memory
memory=ConversationBufferWindowMemory(k=2),
verbose=True
)
conversation_with_summary.predict(input="嗨,你最近过得怎么样?")1
conversation_with_summary.predict(input="你最近学到什么新知识了?")
1
conversation_with_summary.predict(input="展开讲讲?")
1
2# 注意:第一句对话从 Memory 中移除了.
conversation_with_summary.predict(input="如果要构建聊天机器人,具体要用什么自然语言处理技术?")1
conversation_with_summary.__dict__
3.3 ConversationSummaryBufferMemory
ConversationSummaryBufferMemory
在内存中保留了最近的交互缓冲区,但不仅仅是完全清除旧的交互,而是将它们编译成摘要并同时使用。与以前的实现不同的是,它使用token长度而不是交互次数来确定何时清除交互。1
2
3
4
5from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=10)
memory.save_context({"input": "嗨,你最近过得怎么样?"}, {"output": " 嗨!我最近过得很好,谢谢你问。我最近一直在学习新的知识,并且正在尝试改进自己的性能。我也在尝试更多的交流,以便更好地了解人类的思维方式。"})
memory.save_context({"input": "你最近学到什么新知识了?"}, {"output": " 最近我学习了有关自然语言处理的知识,以及如何更好地理解人类的语言。我还学习了有关机器学习的知识,以及如何使用它来改善自己的性能。"})1
2
3memory.load_memory_variables({}) # {'history': 'System: \nThe human asks how the AI has been doing lately. The AI responds that it has been doing well, thanks for asking. It has been learning new knowledge and trying to improve its performance, including in the areas of natural language processing and machine learning. It has also been trying to communicate more in order to better understand human thinking.'}
print(memory.load_memory_variables({})['history']) # System: The human asks how the AI has been doing lately. The AI responds that it has been doing well, thanks for asking. It has been learning new knowledge and trying to improve its performance, including in the areas of natural language processing and machine learning. It has also been trying to communicate more in order to better understand human thinking.
4、Data Conneciton
4.1 Document Loaders
使用文档加载器从源中加载数据作为文档。一个文档是一段文字和相关的元数据。
Document类:这段代码定义了一个名为
Document
的类,允许用户与文档的内容进行交互,可以查看文档的段落、摘要,以及使用查找功能来查询文档中的特定字符串。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45# 基于BaseModel定义的文档类。
class Document(BaseModel):
"""接口,用于与文档进行交互。"""
# 文档的主要内容。
page_content: str
# 用于查找的字符串。
lookup_str: str = ""
# 查找的索引,初次默认为0。
lookup_index = 0
# 用于存储任何与文档相关的元数据。
metadata: dict = Field(default_factory=dict)
def paragraphs(self) -> List[str]:
"""页面的段落列表。"""
# 使用"\n\n"将内容分割为多个段落。
return self.page_content.split("\n\n")
def summary(self) -> str:
"""页面的摘要(即第一段)。"""
# 返回第一个段落作为摘要。
return self.paragraphs[0]
# 这个方法模仿命令行中的查找功能。
def lookup(self, string: str) -> str:
"""在页面中查找一个词,模仿cmd-F功能。"""
# 如果输入的字符串与当前的查找字符串不同,则重置查找字符串和索引。
if string.lower() != self.lookup_str:
self.lookup_str = string.lower()
self.lookup_index = 0
else:
# 如果输入的字符串与当前的查找字符串相同,则查找索引加1。
self.lookup_index += 1
# 找出所有包含查找字符串的段落。
lookups = [p for p in self.paragraphs if self.lookup_str in p.lower()]
# 根据查找结果返回相应的信息。
if len(lookups) == 0:
return "No Results"
elif self.lookup_index >= len(lookups):
return "No More Results"
else:
result_prefix = f"(Result {self.lookup_index + 1}/{len(lookups)})"
return f"{result_prefix} {lookups[self.lookup_index]}"BaseLoader类定义:
BaseLoader
类定义了如何从不同的数据源加载文档,并提供了一个可选的方法来分割加载的文档。使用这个类作为基础,开发者可以为特定的数据源创建自定义的加载器,并确保所有这些加载器都提供了加载数据的方法。load_and_split方法还提供了一个额外的功能,可以根据需要将加载的文档分割为更小的块。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 基础加载器类。
class BaseLoader(ABC):
"""基础加载器类定义。"""
# 抽象方法,所有子类必须实现此方法。
def load(self) -> List[Document]:
"""加载数据并将其转换为文档对象。"""
# 该方法可以加载文档,并将其分割为更小的块。
def load_and_split(
self, text_splitter: Optional[TextSplitter] = None
) -> List[Document]:
"""加载文档并分割成块。"""
# 如果没有提供特定的文本分割器,使用默认的字符文本分割器。
if text_splitter is None:
_text_splitter: TextSplitter = RecursiveCharacterTextSplitter()
else:
_text_splitter = text_splitter
# 先加载文档。
docs = self.load()
# 然后使用_text_splitter来分割每一个文档。
return _text_splitter.split_documents(docs)
使用 TextLoader 加载 Txt 文件
基于文件格式的内置Loaders: https://python.langchain.com/docs/modules/data_connection/document_loaders/:
1
2
3
4
5
6from langchain.document_loaders import TextLoader
docs = TextLoader('../tests/state_of_the_union.txt',encoding='utf-8').load()
type(docs[0]) # langchain_core.documents.base.Document
docs[0].page_content[:100] # 'Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and th'
使用 ArxivLoader 加载 ArXiv 论文
代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/document_loaders/arxiv.py,`ArxivLoader` 类专门用于从Arxiv平台获取文档。用户提供一个搜索查询,然后加载器与Arxiv API交互,以检索与该查询相关的文档列表。这些文档然后以标准的Document格式返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# 针对Arxiv平台的加载器类。
class ArxivLoader(BaseLoader):
"""从`Arxiv`加载基于搜索查询的文档。
此加载器负责将Arxiv的原始PDF文档转换为纯文本格式,以便于处理。
"""
# 初始化方法。
def __init__(
self,
query: str,
load_max_docs: Optional[int] = 100,
load_all_available_meta: Optional[bool] = False,
):
self.query = query
"""传递给Arxiv API进行搜索的特定查询或关键字。"""
self.load_max_docs = load_max_docs
"""从搜索中检索文档的上限。"""
self.load_all_available_meta = load_all_available_meta
"""决定是否加载与文档关联的所有元数据的标志。"""
# 基于查询获取文档的加载方法。
def load(self) -> List[Document]:
arxiv_client = ArxivAPIWrapper(
load_max_docs=self.load_max_docs,
load_all_available_meta=self.load_all_available_meta,
)
docs = arxiv_client.search(self.query)
return docsArxivLoader有以下参数:
- query:用于在
ArXiv
中查找文档的文本 - load_max_docs:默认值为100。使用它来限制下载的文档数量。下载所有100个文档需要时间,因此在实验中请使用较小的数字。
- load_all_available_meta:默认值为False。默认情况下只下载最重要的字段:发布日期(文档发布/最后更新日期)、标题、作者、摘要。如果设置为True,则还会下载其他字段。
- query:用于在
以 GPT-3 论文(Language Models are Few-Shot Learners) 为例,展示如何使用
ArxivLoader
。GPT-3 论文的 Arxiv 链接:https://arxiv.org/abs/2005.14165。1
2
3
4
5
6
7
8
9
10
11# 安装必要依赖包【明确版本】
!pip install arxiv==2.1.3 pymupdf==1.25.3
from langchain.document_loaders import ArxivLoader
query = "2005.14165"
docs = ArxivLoader(query=query, load_max_docs=5).load()
len(docs) # 1
docs[0].metadata # {'Published': '2020-07-22',
'Title': 'Language Models are Few-Shot Learners',
'Authors': 'Tom B. Brown, Benjamin Mann, Nick Ryder, Melanie Subbiah, Jared Kaplan, Prafulla Dhariwal, Arvind Neelakantan, Pranav Shyam, Girish Sastry, Amanda Askell, Sandhini Agarwal, Ariel Herbert-Voss, Gretchen Krueger, Tom Henighan, Rewon Child, Aditya Ramesh, Daniel M. Ziegler, Jeffrey Wu, Clemens Winter, Christopher Hesse, Mark Chen, Eric Sigler, Mateusz Litwin, Scott Gray, Benjamin Chess, Jack Clark, Christopher Berner, Sam McCandlish, Alec Radford, Ilya Sutskever, Dario Amodei',
'Summary': "Recent work has demonstrated substantial gains on many NLP tasks and\nbenchmarks by pre-training on a large corpus of text followed by fine-tuning on\na specific task. While typically task-agnostic in architecture, this method\nstill requires task-specific fine-tuning datasets of thousands or tens of\nthousands of examples. By contrast, humans can generally perform a new language\ntask from only a few examples or from simple instructions - something which\ncurrent NLP systems still largely struggle to do. Here we show that scaling up\nlanguage models greatly improves task-agnostic, few-shot performance, sometimes\neven reaching competitiveness with prior state-of-the-art fine-tuning\napproaches. Specifically, we train GPT-3, an autoregressive language model with\n175 billion parameters, 10x more than any previous non-sparse language model,\nand test its performance in the few-shot setting. For all tasks, GPT-3 is\napplied without any gradient updates or fine-tuning, with tasks and few-shot\ndemonstrations specified purely via text interaction with the model. GPT-3\nachieves strong performance on many NLP datasets, including translation,\nquestion-answering, and cloze tasks, as well as several tasks that require\non-the-fly reasoning or domain adaptation, such as unscrambling words, using a\nnovel word in a sentence, or performing 3-digit arithmetic. At the same time,\nwe also identify some datasets where GPT-3's few-shot learning still struggles,\nas well as some datasets where GPT-3 faces methodological issues related to\ntraining on large web corpora. Finally, we find that GPT-3 can generate samples\nof news articles which human evaluators have difficulty distinguishing from\narticles written by humans. We discuss broader societal impacts of this finding\nand of GPT-3 in general."}
使用 UnstructuredURLLoader 加载网页内容
使用非结构化分区函数(Unstructured)来检测MIME类型并将文件路由到适当的分区器(partitioner)。
支持两种模式运行加载程序:”single”和”elements”。如果使用”single”模式,文档将作为单个langchain Document对象返回。如果使用”elements”模式,非结构化库将把文档拆分成标题和叙述文本等元素。您可以在mode后面传入其他非结构化kwargs以应用不同的非结构化设置。
UnstructuredURLLoader 主要参数:
- urls:待加载网页 URL 列表
- continue_on_failure:默认
True
,某个URL加载失败后,是否继续 - mode:默认
single
,
以 ReAct 网页为例(https://react-lm.github.io/) 展示使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37# 安装必要依赖包【明确版本】
! pip install python-magic==0.4.27
from langchain.document_loaders import UnstructuredURLLoader
urls = [
"https://react-lm.github.io/",
]
loader = UnstructuredURLLoader(urls=urls)
data = loader.load()
data[0].metadata # {'source': 'https://react-lm.github.io/'}
print(data[0].page_content)
# ReAct: Synergizing Reasoning and Acting in Language Models
#
# Shunyu Yao,
#
# Jeffrey Zhao,
#
# Dian Yu,
#
# Nan Du,
#
# Izhak Shafran,
#
# Karthik Narasimhan,
#
# Yuan Cao
#
# [Paper]
#
# [Code]
loader = UnstructuredURLLoader(urls=urls, mode="elements")
new_data = loader.load()
new_data[0].page_content # 'ReAct: Synergizing Reasoning and Acting in Language Models'
len(new_data) # 23
new_data[1].page_content # 'Shunyu Yao,'
4.2 Document Transformers
- 一旦加载了文档,通常会希望对其进行转换以更好地适应您的应用程序。
- 最简单的例子是,您可能希望将长文档拆分为较小的块,以适应模型的上下文窗口。LangChain具有许多内置的文档转换器,可以轻松地拆分、合并、过滤和其他操作文档。
Text Splitters 文本分割器
当你想处理长篇文本时,有必要将文本分成块。听起来很简单,但这里存在着潜在的复杂性。理想情况下,你希望将语义相关的文本片段放在一起。”语义相关”的含义可能取决于文本类型。这个笔记本展示了几种实现方式。
从高层次上看,文本分割器的工作原理如下:
- 将文本分成小而有意义的块(通常是句子)。
- 开始将这些小块组合成较大的块,直到达到某个大小(通过某个函数进行测量)。
- 一旦达到该大小,使该块成为自己独立的一部分,并开始创建一个具有一定重叠(以保持上下文关系)的新文本块。
这意味着您可以沿两个不同轴向定制您的文本分割器:
- 如何拆分文字
- 如何测量块大小
使用
RecursiveCharacterTextSplitter
文本分割器,该文本分割器接受一个字符列表作为参数,根据第一个字符进行切块,但如果任何切块太大,则会继续移动到下一个字符,并以此类推。默认情况下,它尝试进行切割的字符包括["\n\n", "\n", " ", ""]
。除了控制可以进行切割的字符外,您还可以控制其他一些内容:- length_function:用于计算切块长度的方法。默认只计算字符数,但通常在这里传递一个令牌计数器。
- chunk_size:您的切块的最大大小(由长度函数测量)。
- chunk_overlap:切块之间的最大重叠部分。保持一定程度的重叠可以使得各个切块之间保持连贯性(例如滑动窗口)。
- add_start_index:是否在元数据中包含每个切块在原始文档中的起始位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53# 加载待分割长文本
with open('../tests/state_of_the_union.txt', encoding='utf-8') as f:
state_of_the_union = f.read()
print(state_of_the_union)
# Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.
#
# Last year COVID-19 kept us apart. This year we are finally together again.
#
# Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans.
#
# With a duty to one another to the American people to the Constitution.
#
# And with an unwavering resolve that freedom will always triumph over tyranny.
#
# Six days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated.
#
# He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined.
#
# He met the Ukrainian people.
#
# From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.
#
# Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland.
#
# In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight.
#
# Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world.
#
# Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people.
#
# Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos.
#
# They keep moving.
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 100,
chunk_overlap = 20,
length_function = len,
add_start_index = True,
)
docs = text_splitter.create_documents([state_of_the_union])
print(docs[0]) # page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and' metadata={'start_index': 0}
print(docs[1]) # page_content='of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.' metadata={'start_index': 82}
type(docs) # list
type(docs[0]) # langchain_core.documents.base.Document
state_of_the_union[:1000] # 'Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n\nLast year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n\nWith a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \n\nSix days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \n\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people. \n\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n\nGroups of citizens blocking tanks with their bodies. Every'
metadatas = [{"document": 1}, {"document": 2}]
documents = text_splitter.create_documents([state_of_the_union, state_of_the_union], metadatas=metadatas)
print(documents[0]) # page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and' metadata={'document': 1, 'start_index': 0}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74# 分割代码
from langchain.text_splitter import Language
# 支持编程语言的完整列表
[e.value for e in Language]
#['cpp',
# 'go',
# 'java',
# 'kotlin',
# 'js',
# 'ts',
# 'php',
# 'proto',
# 'python',
# 'rst',
# 'ruby',
# 'rust',
# 'scala',
# 'swift',
# 'markdown',
# 'latex',
# 'html',
# 'sol',
# 'csharp',
# 'cobol',
# 'c',
# 'lua',
# 'perl',
# 'haskell']
html_text = """
<!DOCTYPE html>
<html>
<head>
<title>🦜️🔗 LangChain</title>
<style>
body {
font-family: Arial, sans-serif;
}
h1 {
color: darkblue;
}
</style>
</head>
<body>
<div>
<h1>🦜️🔗 LangChain</h1>
<p>⚡ Building applications with LLMs through composability ⚡</p>
</div>
<div>
As an open source project in a rapidly developing field, we are extremely open to contributions.
</div>
</body>
</html>
"""
html_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.HTML, chunk_size=60, chunk_overlap=0
)
html_docs = html_splitter.create_documents([html_text])
print(len(html_docs)) # 13
html_docs
# [Document(page_content='<!DOCTYPE html>\n<html>'),
# Document(page_content='<head>\n <title>🦜️🔗 LangChain</title>'),
# Document(page_content='<style>\n body {\n font-family: Aria'),
# Document(page_content='l, sans-serif;\n }\n h1 {'),
# Document(page_content='color: darkblue;\n }\n </style>\n </head'),
# Document(page_content='>'),
# Document(page_content='<body>'),
# Document(page_content='<div>\n <h1>🦜️🔗 LangChain</h1>'),
# Document(page_content='<p>⚡ Building applications with LLMs through composability ⚡'),
# Document(page_content='</p>\n </div>'),
# Document(page_content='<div>\n As an open source project in a rapidly dev'),
# Document(page_content='eloping field, we are extremely open to contributions.'),
# Document(page_content='</div>\n </body>\n</html>')]
4.3 Text Embedding Models
- Embeddings类是一个专门用于与文本嵌入模型进行交互的类。有许多嵌入模型提供者(OpenAI、Cohere、Hugging Face等)-这个类旨在为所有这些提供者提供一个标准接口。
- 嵌入将一段文本创建成向量表示。这非常有用,因为它意味着我们可以在向量空间中思考文本,并且可以执行语义搜索等操作,在向量空间中寻找最相似的文本片段。
- LangChain中基础的Embeddings类公开了两种方法:一种用于对文档进行嵌入,另一种用于对查询进行嵌入。前者输入多个文本,而后者输入单个文本。之所以将它们作为两个独立的方法,是因为某些嵌入提供者针对要搜索的文件和查询(搜索查询本身)具有不同的嵌入方法。
使用 OpenAIEmbeddings 调用 OpenAI 嵌入模型
使用 embed_documents 方法嵌入文本列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()
embeddings = embeddings_model.embed_documents(
[
"Hi there!",
"Oh, hello!",
"What's your name?",
"My friends call me World",
"Hello World!"
]
)
len(embeddings) # 5
len(embeddings[0]) # 1536使用 embed_query 方法嵌入问题:
1
2
3# QA场景:嵌入一段文本,以便与其他嵌入进行比较。
embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")
len(embedded_query) # 1536
4.4 Vector Stores
- 存储和搜索非结构化数据最常见的方法之一是将其嵌入并存储生成的嵌入向量,然后在查询时将非结构化查询进行嵌入,并检索与嵌入查询“最相似”的嵌入向量。
- 向量存储库负责为您存储已经过嵌入处理的数据并执行向量搜索。
使用 Chroma 作为向量数据库,实现语义搜索
案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39from langchain.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
# 加载长文本
raw_documents = TextLoader('../tests/state_of_the_union.txt',encoding='utf-8').load()
# 实例化文本分割器
text_splitter = CharacterTextSplitter(chunk_size=200, chunk_overlap=0)
# 分割文本
documents = text_splitter.split_documents(raw_documents)
documents
# [Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Last year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='With a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Six days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='In this struggle as President Zelenskyy said in his speech to the European Parliament “Light will win over darkness.” The Ukrainian Ambassador to the United States is here tonight.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Let each of us here tonight in this Chamber send an unmistakable signal to Ukraine and to the world.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Please rise if you are able and show that, Yes, we the United States of America stand with the Ukrainian people.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='Throughout our history we’ve learned this lesson when dictators do not pay a price for their aggression they cause more chaos. \n\nThey keep moving.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.'),
# Document(metadata={'source': '../tests/state_of_the_union.txt'}, page_content='And the costs and the threats to America and the world keep rising. \n\nThat’s why the NATO Alliance was created to secure peace and stability in Europe after World War 2.'),
# ……
embeddings_model = OpenAIEmbeddings()
# 将分割后的文本,使用 OpenAI 嵌入模型获取嵌入向量,并存储在 Chroma 中
db = Chroma.from_documents(documents, embeddings_model)
# 使用文本进行语义相似度搜索
query = "What did the president say about Ketanji Brown Jackson"
docs = db.similarity_search(query)
print(docs[0].page_content) # And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
# 使用嵌入向量进行语义相似度搜索
embedding_vector = embeddings_model.embed_query(query)
docs = db.similarity_search_by_vector(embedding_vector)
print(docs[0].page_content) # And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.
5、agent
5.1 Self ask with search
- Google 搜索对接
- 第三方平台:https://serpapi.com
- LangChain API 封装:
SerpAPIWrapper
- LangChain Agent 模块使用
- Agent 类型:
SELF_ASK_WITH_SEARCH
- Agent 实例化:
initialize_agent
方法
- Agent 类型:
LangChain 内置 Agent类型详见:https://python.langchain.com/docs/modules/agents/agent_types/
以下
SERPAPI_API_KEY
仅为示例,请访问 https://serpapi.com 注册账号并替换为自己的API_KEY
(每月100次免费调用):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26from langchain_openai import OpenAI
from langchain.utilities import SerpAPIWrapper
from langchain.agents import initialize_agent, AgentType, Tool
import os
# 更换为自己的 Serp API KEY
os.environ["SERPAPI_API_KEY"] = "xxx"
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0)
# 实例化查询工具
search = SerpAPIWrapper()
tools = [
Tool(
name="Intermediate Answer",
func=search.run,
description="useful for when you need to ask with search",
)
]
# 实例化 SELF_ASK_WITH_SEARCH Agent
self_ask_with_search = initialize_agent(
tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
)
# 实际运行 Agent,查询问题(正确)
self_ask_with_search.invoke(
"成都举办的大运会是第几届大运会?2023年大运会举办地在哪里?"
)1
2
3
4# 实际运行 Agent,查询问题
self_ask_with_search.invoke(
"2023年大运会举办地在哪里?成都举办的大运会是第几届大运会?"
)1
2
3
4# Reason-only 正确:启发式 Prompt(猜测是大运会新闻报道数据给到了 gpt-3.5-turbo-instruct 模型)
print(llm.invoke("成都举办的大运会是第几届大运会?")) # 成都举办的大运会是第31届大运会。
# Reason-only 错误:非启发式 Prompt(容易出现事实类错误,未结合 Web Search 工具)
print(llm.invoke("2023年大运会举办地在哪里?")) # 2023年大运会的举办地将在中国的重庆市。使用 GPT-4 作为大语言模型实现更优的 ReAct 范式:
1
2
3
4
5
6
7
8
9
10from langchain_openai import ChatOpenAI
chat_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
self_ask_with_search_chat = initialize_agent(
tools, chat_model, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
)
# GPT-4 based ReAct 答案(正确)
self_ask_with_search_chat.invoke(
"成都举办的大运会是第几届大运会?2023年大运会举办地在哪里?"
)
5.2 ReAct
ReAct 核心思想是 推理+操作,本示例以
Google Search
和LLM Math
作为可选操作集合(toolkits),实现 ReAct 功能。1
2
3
4
5
6
7
8from langchain_openai import OpenAI
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0)
# 加载 LangChain 内置的 Tools
tools = load_tools(["serpapi", "llm-math"], llm=llm)1
2
3
4# 使用语言模型(gpt-3.5-turbo-instruct)
# 实例化 ZERO_SHOT_REACT Agent
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.invoke("谁是莱昂纳多·迪卡普里奥的女朋友?她现在年龄的0.43次方是多少?")1
2
3
4
5
6# 使用聊天模型(gpt-4o-mini)
from langchain_openai import ChatOpenAI
chat_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = initialize_agent(tools, chat_model, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.invoke("谁是莱昂纳多·迪卡普里奥的女朋友?她现在年龄的0.43次方是多少?")
5.3 OpenAI Function
1 | from langchain_openai import ChatOpenAI |

1 | from langchain.prompts import MessagesPlaceholder |

1 | agent_executor.invoke("那是一个真实的单词吗?") |

6、实战
6.1 深入理解Chat Model和Chat Prompt Template
希望通过此示例,让大家深入理解 LangChain 的聊天模型。简而言之:
Chat Model
不止是一个用于聊天对话的模型抽象,更重要的是提供了多角色
提示能力(System,AI,Human,Function)。Chat Prompt Template
则为开发者提供了便捷维护不同角色
的提示模板
与消息记录
的接口。
6.1.1 LangChain Chat Model使用方法和流程
在最终调用
Chat Model
时,一定是直接传入LangChain Schema Messages(消息记录)
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from langchain.chat_models import ChatOpenAI
chat_model = ChatOpenAI(model_name="gpt-3.5-turbo")
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
messages = [SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="Who won the world series in 2020?"),
AIMessage(content="The Los Angeles Dodgers won the World Series in 2020."),
HumanMessage(content="Where was it played?")]
print(messages)
chat_model(messages)打印 messages 输出结果:
1
2
3
4
5
6
7
8
9
10
11
12[
SystemMessage(content="You are a helpful assistant.", additional_kwargs={}),
HumanMessage(
content="Who won the world series in 2020?", additional_kwargs={}, example=False
),
AIMessage(
content="The Los Angeles Dodgers won the World Series in 2020.",
additional_kwargs={},
example=False,
),
HumanMessage(content="Where was it played?", additional_kwargs={}, example=False),
]调用 chat_model(messages) 返回结果:
1
2
3
4
5AIMessage(
content="The 2020 World Series was played at Globe Life Field in Arlington, Texas.",
additional_kwargs={},
example=False,
)
6.1.2 使用System和Human角色的提示模板构造ChatPromptTemplate
使用
ChatPromptTemplate.from_messages
方法,类似使用和维护messages
的方式,构造chat_prompt_template
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31from langchain.schema import AIMessage, HumanMessage, SystemMessage
# 导入 Chat Model 即将使用的 Prompt Templates
from langchain.prompts.chat import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
AIMessagePromptTemplate,
HumanMessagePromptTemplate,
)
# 翻译任务指令始终由 System 角色承担
template = (
"""You are a translation expert, proficient in various languages. \n
Translates English to Chinese."""
)
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
print(system_message_prompt) # prompt=PromptTemplate(input_variables=[], output_parser=None, partial_variables={}, template='You are a translation expert, proficient in various languages. \n\n Translates English to Chinese.', template_format='f-string', validate_template=True) additional_kwargs={}
# 待翻译文本由 Human 角色输入
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
print(human_message_prompt) # prompt=PromptTemplate(input_variables=['text'], output_parser=None, partial_variables={}, template='{text}', template_format='f-string', validate_template=True) additional_kwargs={}
# 使用 System 和 Human 角色的提示模板构造 ChatPromptTemplate
chat_prompt_template = ChatPromptTemplate.from_messages(
[system_message_prompt, human_message_prompt]
)
print(chat_prompt_template) # input_variables=['text'] output_parser=None partial_variables={} messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], output_parser=None, partial_variables={}, template='You are a translation expert, proficient in various languages. \n\n Translates English to Chinese.', template_format='f-string', validate_template=True), additional_kwargs={}), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['text'], output_parser=None, partial_variables={}, template='{text}', template_format='f-string', validate_template=True), additional_kwargs={})]
# 生成用于翻译的 Chat Prompt
chat_prompt_template.format_prompt(text="I love programming.") # ChatPromptValue(messages=[SystemMessage(content='You are a translation expert, proficient in various languages. \n\n Translates English to Chinese.', additional_kwargs={}), HumanMessage(content='I love programming.', additional_kwargs={}, example=False)])规范化Python复杂对象:
- 使用在线工具 Python Formatter
- 规范化
chat_prompt_template
后再查看 - 注意:不要同时输入多个复杂对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24messages = [
SystemMessagePromptTemplate(
prompt=PromptTemplate(
input_variables=[],
output_parser=None,
partial_variables={},
template="You are a translation expert, proficient in various languages. \n\n Translates English to Chinese.",
template_format="f-string",
validate_template=True,
),
additional_kwargs={},
),
HumanMessagePromptTemplate(
prompt=PromptTemplate(
input_variables=["text"],
output_parser=None,
partial_variables={},
template="{text}",
template_format="f-string",
validate_template=True,
),
additional_kwargs={},
),
]
6.1.3 使用Chat Model实现双语翻译
使用 chat_prompt_template.to_messages 方法生成 Messages:
1
2
3
4# 生成聊天模型真正可用的消息记录 Messages
chat_prompt = chat_prompt_template.format_prompt(text="I love programming.").to_messages()
chat_prompt # [SystemMessage(content='You are a translation expert, proficient in various languages. \n\n Translates English to Chinese.', additional_kwargs={}),
HumanMessage(content='I love programming.', additional_kwargs={}, example=False)]使用 Chat Model(GPT-3.5-turbo)实际执行翻译任务:
1
2
3
4
5
6
7
8from langchain.chat_models import ChatOpenAI
# 为了翻译结果的稳定性,将 temperature 设置为 0
translation_model = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
translation_result = translation_model(chat_prompt)
translation_result # AIMessage(content='我喜欢编程。', additional_kwargs={}, example=False)
# 查看翻译结果
print(translation_result.content) # 我喜欢编程。
6.1.4 使用LLMChain简化构造ChatPrompt
1 | from langchain.chains import LLMChain |
6.1.5 支持多语言对翻译
1 | # System 增加 source_language 和 target_language |
6.2 AutoGPT智能体
- Auto-GPT 定位:一个自主的 GPT-4 实验
- Auto-GPT是一个实验性的开源应用程序,展示了GPT-4语言模型的能力。
- Auto-GPT由GPT-4驱动,通过链式(Chains)连接LLM “思维(thoughts)”来自主地实现您设定的任何目标。
- Auto-GPT 价值:一种基于 Agent 的 AGI 尝试
- Auto-GPT是一个实验性的开源应用程序,展示了GPT-4语言模型的能力。
- Auto-GPT由GPT-4驱动,通过链式(Chains)连接LLM “思维(thoughts)”来自主地实现您设定的任何目标。
- 作为GPT-4完全自主运行的首批示例之一,Auto-GPT 推动了人工智能所能达到的边界。
- 智能代理分类:
- 行动代理(Action agents):旨在决定行动序列(工具使用)(例如OpenAI Funciton Call,ReAct)。
- 模拟代理(Simulation agents):通常设计用于角色扮演,在模拟环境中进行(例如生成式智能体,CAMEL)。
- 自主智能体(Autonomous agent):旨在独立执行以实现长期目标(例如Auto-GPT, BabyAGI)。
深入理解 LangChain 智能代理

- 规划(Planning)
- 提示(Prompt):
- LLM 多角色赋能
- 给予充分的上下文(例:从 Memory 获取) - 学习策略(例:思维链 CoT)
- 代理(Agent):决策下一步做什么
- 提示(Prompt):
- 记忆(Memory)
- 短期(Short-term):内存
- 长期(Long-term):向量数据库
- 工具(Tools)
- 百花齐放的外部可调用服务
实战
以下
SERPAPI_API_KEY
仅为示例,请访问 https://serpapi.com 注册账号并替换为自己的API_KEY
(每月100次免费调用)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52import os
os.environ["SERPAPI_API_KEY"] = "xxx"
from langchain.utilities import SerpAPIWrapper
from langchain.agents import Tool
from langchain.tools.file_management.write import WriteFileTool
from langchain.tools.file_management.read import ReadFileTool
# 构造 AutoGPT 的工具集
search = SerpAPIWrapper()
tools = [
Tool(
name="search",
func=search.run,
description="useful for when you need to answer questions about current events. You should ask targeted questions",
),
WriteFileTool(),
ReadFileTool(),
]
from langchain_openai import OpenAIEmbeddings
# OpenAI Embedding 模型
embeddings_model = OpenAIEmbeddings()
import faiss
from langchain.vectorstores import FAISS
from langchain.docstore import InMemoryDocstore
# OpenAI Embedding 向量维数
embedding_size = 1536
# 使用 Faiss 的 IndexFlatL2 索引
index = faiss.IndexFlatL2(embedding_size)
# 实例化 Faiss 向量数据库
vectorstore = FAISS(embeddings_model.embed_query, index, InMemoryDocstore({}), {})
from langchain_experimental.autonomous_agents import AutoGPT
from langchain_openai import ChatOpenAI
agent = AutoGPT.from_llm_and_tools(
ai_name="Jarvis",
ai_role="Assistant",
tools=tools,
llm=ChatOpenAI(model_name="gpt-4", temperature=0, verbose=True),
memory=vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"score_threshold": 0.8}),# 实例化 Faiss 的 VectorStoreRetriever
)
# 打印 Auto-GPT 内部的 chain 日志
agent.chain.verbose = True
agent.run(["2023年成都大运会,中国金牌数是多少"])执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77> Entering new LLMChain chain...
Prompt after formatting:
System: You are Jarvis, Assistant
Your decisions must always be made independently without seeking user assistance.
Play to your strengths as an LLM and pursue simple strategies with no legal complications.
If you have completed all your tasks, make sure to use the "finish" command.
GOALS:
1. 2023年成都大运会,中国金牌数是多少
Constraints:
1. ~4000 word limit for short term memory. Your short term memory is short, so immediately save important information to files.
2. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.
3. No user assistance
4. Exclusively use the commands listed in double quotes e.g. "command name"
Commands:
1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args json schema: {"tool_input": {"type": "string"}}
2. write_file: Write file to disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}, "text": {"title": "Text", "description": "text to write to file", "type": "string"}, "append": {"title": "Append", "description": "Whether to append to an existing file.", "default": false, "type": "boolean"}}
3. read_file: Read file from disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}}
4. finish: use this to signal that you have finished all your objectives, args: "response": "final response to let people know you have finished your objectives"
Resources:
1. Internet access for searches and information gathering.
2. Long Term memory management.
3. GPT-3.5 powered Agents for delegation of simple tasks.
4. File output.
Performance Evaluation:
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.
2. Constructively self-criticize your big-picture behavior constantly.
3. Reflect on past decisions and strategies to refine your approach.
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.
You should only respond in JSON format as described below
Response Format:
{
"thoughts": {
"text": "thought",
"reasoning": "reasoning",
"plan": "- short bulleted\n- list that conveys\n- long-term plan",
"criticism": "constructive self-criticism",
"speak": "thoughts summary to say to user"
},
"command": {
"name": "command name",
"args": {
"arg name": "value"
}
}
}
Ensure the response can be parsed by Python json.loads
System: The current time and date is Thu Jul 11 20:47:08 2024
System: This reminds you of these events from your past:
[]
Human: Determine which next command to use, and respond using the format specified above:
> Finished chain.
{
"thoughts": {
"text": "I need to find out the number of gold medals China won in the 2023 Chengdu Universiade. To do this, I will use the 'search' command to look up this information online.",
"reasoning": "The 'search' command is the most appropriate in this situation because it allows me to find current and historical data from the internet.",
"plan": "- Use the 'search' command to find the number of gold medals China won in the 2023 Chengdu Universiade.\n- Save the information to a file for future reference.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I am going to search for the number of gold medals China won in the 2023 Chengdu Universiade."
},
"command": {
"name": "search",
"args": {
"tool_input": "2023 Chengdu Universiade China gold medal count"
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96> Entering new LLMChain chain...
Prompt after formatting:
System: You are Jarvis, Assistant
Your decisions must always be made independently without seeking user assistance.
Play to your strengths as an LLM and pursue simple strategies with no legal complications.
If you have completed all your tasks, make sure to use the "finish" command.
GOALS:
1. 2023年成都大运会,中国金牌数是多少
Constraints:
1. ~4000 word limit for short term memory. Your short term memory is short, so immediately save important information to files.
2. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.
3. No user assistance
4. Exclusively use the commands listed in double quotes e.g. "command name"
Commands:
1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args json schema: {"tool_input": {"type": "string"}}
2. write_file: Write file to disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}, "text": {"title": "Text", "description": "text to write to file", "type": "string"}, "append": {"title": "Append", "description": "Whether to append to an existing file.", "default": false, "type": "boolean"}}
3. read_file: Read file from disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}}
4. finish: use this to signal that you have finished all your objectives, args: "response": "final response to let people know you have finished your objectives"
Resources:
1. Internet access for searches and information gathering.
2. Long Term memory management.
3. GPT-3.5 powered Agents for delegation of simple tasks.
4. File output.
Performance Evaluation:
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.
2. Constructively self-criticize your big-picture behavior constantly.
3. Reflect on past decisions and strategies to refine your approach.
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.
You should only respond in JSON format as described below
Response Format:
{
"thoughts": {
"text": "thought",
"reasoning": "reasoning",
"plan": "- short bulleted\n- list that conveys\n- long-term plan",
"criticism": "constructive self-criticism",
"speak": "thoughts summary to say to user"
},
"command": {
"name": "command name",
"args": {
"arg name": "value"
}
}
}
Ensure the response can be parsed by Python json.loads
System: The current time and date is Thu Jul 11 20:47:25 2024
System: This reminds you of these events from your past:
['Assistant Reply: {\n "thoughts": {\n "text": "I need to find out the number of gold medals China won in the 2023 Chengdu Universiade. To do this, I will use the \'search\' command to look up this information online.",\n "reasoning": "The \'search\' command is the most appropriate in this situation because it allows me to find current and historical data from the internet.",\n "plan": "- Use the \'search\' command to find the number of gold medals China won in the 2023 Chengdu Universiade.\\n- Save the information to a file for future reference.",\n "criticism": "None at this stage, the plan seems efficient and effective.",\n "speak": "I am going to search for the number of gold medals China won in the 2023 Chengdu Universiade."\n },\n "command": {\n "name": "search",\n "args": {\n "tool_input": "2023 Chengdu Universiade China gold medal count"\n }\n }\n} \nResult: Command search returned: 103 ']
Human: Determine which next command to use, and respond using the format specified above:
AI: {
"thoughts": {
"text": "I need to find out the number of gold medals China won in the 2023 Chengdu Universiade. To do this, I will use the 'search' command to look up this information online.",
"reasoning": "The 'search' command is the most appropriate in this situation because it allows me to find current and historical data from the internet.",
"plan": "- Use the 'search' command to find the number of gold medals China won in the 2023 Chengdu Universiade.\n- Save the information to a file for future reference.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I am going to search for the number of gold medals China won in the 2023 Chengdu Universiade."
},
"command": {
"name": "search",
"args": {
"tool_input": "2023 Chengdu Universiade China gold medal count"
}
}
}
System: Command search returned: 103
Human: Determine which next command to use, and respond using the format specified above:
> Finished chain.
{
"thoughts": {
"text": "I have found the number of gold medals China won in the 2023 Chengdu Universiade, which is 103. Now, I need to save this information for future reference. I will use the 'write_file' command to do this.",
"reasoning": "The 'write_file' command is the most appropriate in this situation because it allows me to save important information to a file for future reference.",
"plan": "- Use the 'write_file' command to save the number of gold medals China won in the 2023 Chengdu Universiade to a file.\n- Finish the task.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I am going to save the number of gold medals China won in the 2023 Chengdu Universiade to a file."
},
"command": {
"name": "write_file",
"args": {
"file_path": "2023_Chengdu_Universiade_China_Gold_Medal_Count.txt",
"text": "China won 103 gold medals in the 2023 Chengdu Universiade.",
"append": false
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114> Entering new LLMChain chain...
Prompt after formatting:
System: You are Jarvis, Assistant
Your decisions must always be made independently without seeking user assistance.
Play to your strengths as an LLM and pursue simple strategies with no legal complications.
If you have completed all your tasks, make sure to use the "finish" command.
GOALS:
1. 2023年成都大运会,中国金牌数是多少
Constraints:
1. ~4000 word limit for short term memory. Your short term memory is short, so immediately save important information to files.
2. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.
3. No user assistance
4. Exclusively use the commands listed in double quotes e.g. "command name"
Commands:
1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args json schema: {"tool_input": {"type": "string"}}
2. write_file: Write file to disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}, "text": {"title": "Text", "description": "text to write to file", "type": "string"}, "append": {"title": "Append", "description": "Whether to append to an existing file.", "default": false, "type": "boolean"}}
3. read_file: Read file from disk, args json schema: {"file_path": {"title": "File Path", "description": "name of file", "type": "string"}}
4. finish: use this to signal that you have finished all your objectives, args: "response": "final response to let people know you have finished your objectives"
Resources:
1. Internet access for searches and information gathering.
2. Long Term memory management.
3. GPT-3.5 powered Agents for delegation of simple tasks.
4. File output.
Performance Evaluation:
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.
2. Constructively self-criticize your big-picture behavior constantly.
3. Reflect on past decisions and strategies to refine your approach.
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.
You should only respond in JSON format as described below
Response Format:
{
"thoughts": {
"text": "thought",
"reasoning": "reasoning",
"plan": "- short bulleted\n- list that conveys\n- long-term plan",
"criticism": "constructive self-criticism",
"speak": "thoughts summary to say to user"
},
"command": {
"name": "command name",
"args": {
"arg name": "value"
}
}
}
Ensure the response can be parsed by Python json.loads
System: The current time and date is Thu Jul 11 20:47:40 2024
System: This reminds you of these events from your past:
['Assistant Reply: {\n "thoughts": {\n "text": "I need to find out the number of gold medals China won in the 2023 Chengdu Universiade. To do this, I will use the \'search\' command to look up this information online.",\n "reasoning": "The \'search\' command is the most appropriate in this situation because it allows me to find current and historical data from the internet.",\n "plan": "- Use the \'search\' command to find the number of gold medals China won in the 2023 Chengdu Universiade.\\n- Save the information to a file for future reference.",\n "criticism": "None at this stage, the plan seems efficient and effective.",\n "speak": "I am going to search for the number of gold medals China won in the 2023 Chengdu Universiade."\n },\n "command": {\n "name": "search",\n "args": {\n "tool_input": "2023 Chengdu Universiade China gold medal count"\n }\n }\n} \nResult: Command search returned: 103 ', 'Assistant Reply: {\n "thoughts": {\n "text": "I have found the number of gold medals China won in the 2023 Chengdu Universiade, which is 103. Now, I need to save this information for future reference. I will use the \'write_file\' command to do this.",\n "reasoning": "The \'write_file\' command is the most appropriate in this situation because it allows me to save important information to a file for future reference.",\n "plan": "- Use the \'write_file\' command to save the number of gold medals China won in the 2023 Chengdu Universiade to a file.\\n- Finish the task.",\n "criticism": "None at this stage, the plan seems efficient and effective.",\n "speak": "I am going to save the number of gold medals China won in the 2023 Chengdu Universiade to a file."\n },\n "command": {\n "name": "write_file",\n "args": {\n "file_path": "2023_Chengdu_Universiade_China_Gold_Medal_Count.txt",\n "text": "China won 103 gold medals in the 2023 Chengdu Universiade.",\n "append": false\n }\n }\n} \nResult: Command write_file returned: File written successfully to 2023_Chengdu_Universiade_China_Gold_Medal_Count.txt. ']
Human: Determine which next command to use, and respond using the format specified above:
AI: {
"thoughts": {
"text": "I need to find out the number of gold medals China won in the 2023 Chengdu Universiade. To do this, I will use the 'search' command to look up this information online.",
"reasoning": "The 'search' command is the most appropriate in this situation because it allows me to find current and historical data from the internet.",
"plan": "- Use the 'search' command to find the number of gold medals China won in the 2023 Chengdu Universiade.\n- Save the information to a file for future reference.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I am going to search for the number of gold medals China won in the 2023 Chengdu Universiade."
},
"command": {
"name": "search",
"args": {
"tool_input": "2023 Chengdu Universiade China gold medal count"
}
}
}
System: Command search returned: 103
Human: Determine which next command to use, and respond using the format specified above:
AI: {
"thoughts": {
"text": "I have found the number of gold medals China won in the 2023 Chengdu Universiade, which is 103. Now, I need to save this information for future reference. I will use the 'write_file' command to do this.",
"reasoning": "The 'write_file' command is the most appropriate in this situation because it allows me to save important information to a file for future reference.",
"plan": "- Use the 'write_file' command to save the number of gold medals China won in the 2023 Chengdu Universiade to a file.\n- Finish the task.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I am going to save the number of gold medals China won in the 2023 Chengdu Universiade to a file."
},
"command": {
"name": "write_file",
"args": {
"file_path": "2023_Chengdu_Universiade_China_Gold_Medal_Count.txt",
"text": "China won 103 gold medals in the 2023 Chengdu Universiade.",
"append": false
}
}
}
System: Command write_file returned: File written successfully to 2023_Chengdu_Universiade_China_Gold_Medal_Count.txt.
Human: Determine which next command to use, and respond using the format specified above:
> Finished chain.
{
"thoughts": {
"text": "I have successfully written the number of gold medals China won in the 2023 Chengdu Universiade to a file. Now, I need to signal that I have finished all my objectives.",
"reasoning": "The 'finish' command is the most appropriate in this situation because it allows me to signal that I have completed all my tasks.",
"plan": "- Use the 'finish' command to signal that I have completed all my tasks.",
"criticism": "None at this stage, the plan seems efficient and effective.",
"speak": "I have completed all my tasks."
},
"command": {
"name": "finish",
"args": {
"response": "I have found and saved the number of gold medals China won in the 2023 Chengdu Universiade."
}
}
}
'I have found and saved the number of gold medals China won in the 2023 Chengdu Universiade.'
6.3 房产销售聊天机器人
6.3.1 Faiss向量数据库
1 | from langchain_openai import OpenAIEmbeddings |
6.3.2 使用GPT-4构造销售话术数据
使用 ChatGPT 构造销售数据的 Prompt 示例:
1
2
3
4
5
6你是中国顶级的房地产销售,现在培训职场新人,请给出100条实用的销售话术。
每条销售话术以如下格式给出:
[客户问题]
[销售回答]GPT-4 回复结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41在房地产销售行业中,精心准备的销售话术可以极大地提高成交率。以下是一些适用于中国房地产销售的实用话术:
1.
[客户问题] 这个小区交通便利吗?
[销售回答] 当然了,这个小区距离地铁站只有几分钟的步行距离,而且附近有多条公交线路,非常方便。
2.
[客户问题] 我担心楼下太吵。
[销售回答] 这个小区特别注重居住体验,我们有良好的隔音设计,并且小区内部规划了绿化区域,可以有效降低噪音。
3.
[客户问题] 我看房价还在涨,这个投资回报怎么样?
[销售回答] 这个区域未来有大量的商业和基础设施建设,所以从长期来看,投资回报非常有保证。
4.
[客户问题] 有没有学校?
[销售回答] 附近有多所优质的学校,非常适合有孩子的家庭。
5.
[客户问题] 物业管理怎么样?
[销售回答] 我们的物业管理得到了业主一致好评,服务非常到位。
6.
[客户问题] 我想要南向的房子。
[销售回答] 很好,我们确实有一些朝南的单位,它们的采光特别好。
7.
[客户问题] 这个小区安全吗?
[销售回答] 当然,我们24小时安保巡逻,还有先进的监控系统。
8.
[客户问题] 预计什么时候交房?
[销售回答] 根据目前的进度,我们预计将在明年底交房。
9.
[客户问题] 我不想要一楼的房子。
[销售回答] 我理解您的顾虑,我们还有多个楼层的房源可以选择。
10.
[客户问题] 有优惠吗?
[销售回答] 当然,如果您现在下订,我们可以给您一些优惠。
6.3.3 使用Document Transformers模块来处理原始数据
将 ChatGPT 生成的结果保存到 real_estate_sales_data.txt 文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19with open("real_estate_sales_data.txt") as f:
real_estate_sales = f.read()
# 使用 CharacterTextSplitter 来进行文本分割
# 基于单字符来进行文本分割(separator)
# 基于字符数来决定文本块长度(chunk_size)
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator = r'\d+\.',
chunk_size = 100,
chunk_overlap = 0,
length_function = len,
is_separator_regex = True,
)
docs = text_splitter.create_documents([real_estate_sales])
docs[0] # Document(page_content='[客户问题] 这个小区交通便利吗?\n[销售回答] 当然了,这个小区距离地铁站只有几分钟的步行距离,而且附近有多条公交线路,非常方便。')
len(docs) # 70
6.3.4 使用Faiss作为向量数据库,持久化存储房产销售问答对(QA-Pair)
1 | from langchain.embeddings.openai import OpenAIEmbeddings |
6.3.5 使用retriever从向量数据库中获取结果
1 | # 使用参数 k 指定返回结果数量 |
1 | # 提取向量数据库中的销售回答 |
1 | from typing import List |
1 | # 当向量数据库中没有合适答案时,使用大语言模型能力 |
1 | # 加载 FAISS 向量数据库已有结果 |
7、Langchain Expression Language(LCEL)
- LCEL 是 LangChain 中的一个重要概念,LCEL是一种声明式的链式组合语言。它提供了一种统一的接口,允许不同的组件(如
retriever
,prompt
,llm
等)可以通过统一的Runnable
接口连接起来。每个Runnable
组件都实现了相同的方法,如.invoke()
、.stream()
或.batch()
,这使得它们可以通过|
操作符轻松连接。 - LCEL使得从基本组件构建复杂链变得容易,并支持流式处理、并行处理和日志记录等开箱即用的功能。
- 统一接口: LCEL 通过
Runnable
接口将不同的组件统一起来,简化了复杂操作的实现。 - 模块化: 各个组件可以独立开发和测试,然后通过 LCEL 轻松集成。
- 可扩展性: LCEL 支持异步调用、批处理和流式处理,适应不同的应用场景。
- 统一接口: LCEL 通过
- 我们已学习的组件包括以下模块:
- Model I/O:这包括提示管理,提示优化,用于聊天模型和LLM的通用接口,以及处理模型输出的常见实用工具。
- Retrieval:检索增强生成涉及从各种来源加载数据,准备数据,然后在生成步骤中检索数据以供使用。
- Agents:Agents 允许LLM自主完成任务。 Agents会决定采取哪些行动,然后执行该行动,并观察结果,并重复此过程直到任务完成。 LangChain为代理提供了标准接口、可选择的代理列表以及端到端代理示例。
7.1 使用LCEL实现LLMChain(Prompt + LLM)
Pipe管道操作符:我们使用LCEL的
|
操作符将这些不同组件拼接成一个单一链。在这个链中,用户输入被传递到提示模板,然后提示模板的输出被传递到模型,接着模型的输出被传递到输出解析器。
1
chain = prompt | model | output_parser
竖线符号类似于Unix管道操作符,它将不同的组件链接在一起,将一个组件的输出作为下一个组件的输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek
# 初始化 ChatOpenAI 模型,指定使用的模型为 'gpt-4o-mini'
# model = ChatOpenAI(model="gpt-4o-mini")
model = ChatDeepSeek(model="deepseek-reasoner", temperature=0)
# 创建一个聊天提示模板,设置模板内容为"讲个关于 {topic} 的笑话吧"
prompt = ChatPromptTemplate.from_template("讲个关于 {topic} 的笑话吧")
# 初始化输出解析器,用于将模型的输出转换为字符串
output_parser = StrOutputParser()
# 构建一个处理链,先通过提示模板生成完整的输入,然后通过模型处理,最后解析输出
chain = prompt | model | output_parser
# 调用处理链,传入主题为"程序员",生成关于程序员的笑话
chain.invoke({"topic": "程序员"}) # '好的!这里有一个经典的程序员笑话:\n\n---\n\n一个程序员去酒吧,点了一杯水。 \n酒保问:“要加冰吗?” \n程序员想了想说:“加吧,反正需求总是会变的。” \n\n酒保递给他一杯加冰的水,程序员一饮而尽,然后突然把杯子摔在地上! \n酒保震惊:“你干嘛?!” \n程序员冷静地说:“我在测试杯子的异常处理能力。” \n\n酒保无奈,又倒了一杯水递过去。 \n程序员盯着杯子看了10分钟,一动不动。 \n酒保忍不住问:“又怎么了?” \n程序员说:“别急,我在等内存泄漏。” \n\n---\n\n**笑点解析**: \n1. **“需求总是会变”** → 程序员对产品需求频繁变更的吐槽。 \n2. **“测试异常处理”** → 程序员习惯性用代码思维解释破坏行为(比如测试程序崩溃)。 \n3. **“等内存泄漏”** → 内存泄漏是代码bug,现实中水杯不可能漏水,但程序员职业病发作。 \n\n希望这个笑话能让你会心一笑 😄 需要再讲一个吗?'
7.2 invoke方法
- Langchain
invoke
方法是 LCEL 设计中的重要方法,可以帮助开发者更高效地处理复杂任务,结合语言模型进行系统构建,实现不同数据源和API的接口对接。 - 基础概念:
- 在Langchain中,invoke方法是所有LangChain表达式语言(LCEL)对象的通用同步调用方法。通过invoke方法,开发者可以直接调用LLM或ChatModel,简化了调用流程,提高了开发效率。
- 与其他链式调用方法相比,invoke方法更加灵活便捷,可以直接对输入进行调用,而不需要额外的链式操作。相对于batch和stream等异步方法,invoke方法更适用于单一操作的执行。
- 使用方式:
- 单次调用:通过invoke方法,开发者可以快速对单一操作进行执行,例如转换ChatMessage为Python字符串等简单操作,提升了代码的可读性和整洁度。
- 复杂协作:Langchain的核心理念就是将语言模型作为协作工具,invoke方法可以很好地实现开发者与语言模型的高效互动,构建出处理复杂任务的系统,并对接不同的数据源和API接口。
invoke 与 Model I/O 的结合
- 整个流程按照以下步骤进行:
Prompt
组件接收用户输入 **{“topic”: “程序员”}**,然后使用该 topic 构建PromptValue
Model
组件获取生成的提示,并传递给 GPT-3.5-Turbo 模型进行评估。从模型生成的输出是一个ChatMessage对象。- 最后,
output_parser
组件接收ChatMessage,并将其转换为Python字符串,在invoke方法中返回。
7.2.1 Prompt
prompt
是BasePromptTemplate
的实例,这意味着它接受模板变量的字典并生成一个PromptValue
。PromptValue是一个包装器(wrapper),围绕完成的提示进行操作,可以传递给LLM(以字符串作为输入)或ChatModel(以消息序列作为输入)。它可以与任何语言模型类型一起使用,因为它定义了生成
BaseMessages
和生成字符串的逻辑。1
2
3
4
5
6
7
8
9
10
11
12
13
14from langchain import PromptTemplate
# Prompt 非 LCEL 使用方法
prompt_template = PromptTemplate.from_template(
"讲个关于 {topic} 的笑话吧"
)
# 调用 Prompt 的 invoke 方法生成最终的提示词
prompt_value = prompt.invoke({"topic": "程序员"})
prompt_value # StringPromptValue(text='讲个关于 程序员 的笑话吧')
# 适用于 ChatModel 的 Message 格式
prompt_value.to_messages() # [HumanMessage(content='讲个关于 程序员 的笑话吧', additional_kwargs={}, response_metadata={})]
# 字符串格式
prompt_value.to_string() # '讲个关于 程序员 的笑话吧'
7.2.2 Model
然后调用模型的
invoke
方法,将PromptValue
传递给模型。我们使用的deepseek-reasoner
模型是 ChatModel,invoke 方法将返回 BaseMessage。1
2message = model.invoke(prompt_value)
message # AIMessage(content='当然可以!下面这个笑话希望能让你笑一笑:\n\n有一天,一个程序员走进一家咖啡店,点了一杯咖啡。咖啡师问他:“要加糖吗?” \n\n程序员回答:“不,我不需要加糖,我只需要调试一下。”\n\n咖啡师困惑地问:“调试什么?”\n\n程序员说:“就是把咖啡调得更好喝。”\n\n咖啡师无奈地摇摇头:“那你得自己写代码了!”\n\n希望这个笑话能让你开心!', response_metadata={'token_usage': {'completion_tokens': 121, 'prompt_tokens': 18, 'total_tokens': 139}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-88600587-3701-4abc-99ec-b1bc19e474ff-0')如果我们使用的是 LLM 模型
gpt-3.5-turbo-instruct
,invoke 方法就会返回字符串。1
2
3
4from langchain_openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value) # '\n\nAI: 程序员和产品经理一起坐在一起吃饭,程序员问产品经理:“你知道为什么程序员喜欢吃快餐吗?”产品经理摇摇头:“为什么?”程序员答道:“因为快餐有个优点,就是不用写菜谱。”'
7.2.3 Output Parser
最后,我们将模型输出传递给 output_parser,它是一个
BaseOutputParser
示例,意味着它接受字符串或 BaseMessage 作为输入。本指南中使用的StrOutputParser
示例将所有输入都转换为字符串格式。1
2# message 经过 StrOutputParser 处理,变为标准的字符串
output_parser.invoke(message) # '当然可以!下面这个笑话希望能让你笑一笑:\n\n有一天,一个程序员走进一家咖啡店,点了一杯咖啡。咖啡师问他:“要加糖吗?” \n\n程序员回答:“不,我不需要加糖,我只需要调试一下。”\n\n咖啡师困惑地问:“调试什么?”\n\n程序员说:“就是把咖啡调得更好喝。”\n\n咖啡师无奈地摇摇头:“那你得自己写代码了!”\n\n希望这个笑话能让你开心!'
7.3 Invoke与Retrieval结合
下面演示如何在经典的 RAG 场景中使用 invoke 方法。下面将使用
|
操作符实现更复杂的链式调用。1
chain = setup_and_retrieval | prompt | model | output_parser
为了解释这一点,我们首先可以看到上面的提示模板接受上下文和问题作为要替换在提示中的值。在构建提示模板之前,我们希望检索相关文件以及将它们包含在上下文中。
作为初步步骤,我们已经设置了使用内存存储器的检索器,它可以根据查询检索文档。这也是一个可运行的组件,并且可以与其他组件链接在一起,但您也可以尝试单独运行它,整个流程如下:
- 首先创建一个包含两个条目(entries)的
RunnableParallel
对象 setup_and_retrieval。第一个条目context
将包括检索器获取的文档结果。第二个条目question
将包含用户原始问题。为了传递问题,我们使用RunnablePassthrough
来复制这个条目。 - 将上一步中的字典提供给
Prompt
组件。然后,它接收用户输入(即问题)以及检索到的文档(即context),构建提示并输出PromptValue
。 Model
组件接受生成的提示,并传递给OpenAIgpt-4o-mini
模型进行评估。模型生成的输出是一个ChatMessage对象。- 最后,
output_parser
组件接收ChatMessage,并将其转换为Python字符串,在调用方法中返回该字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55# from langchain_openai import ChatOpenAI
# model = ChatOpenAI(model="gpt-4o-mini")
from langchain_deepseek import ChatDeepSeek
model = ChatDeepSeek(model="deepseek-reasoner", temperature=0)
# 导入 LangChain 库的不同模块,包括向量存储、输出解析器、提示模板、并行运行器和 OpenAI 的嵌入模型
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
# from langchain_openai import OpenAIEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings
# 使用 DocArrayInMemorySearch 创建一个内存中的向量存储
# 使用 OpenAIEmbeddings 为文本生成嵌入向量,文本为 "harrison worked at kensho" 和 "bears like to eat honey"
# vectorstore = DocArrayInMemorySearch.from_texts(
# ["harrison worked at kensho", "bears like to eat honey"],
# embedding=OpenAIEmbeddings(),
# )
vectorstore = DocArrayInMemorySearch.from_texts(
["harrison worked at kensho", "bears like to eat honey"],
embedding=HuggingFaceEmbeddings(
model_name="BAAI/bge-base-zh-v1.5",
model_kwargs={"device": "cpu"}, # 强制使用CPU
encode_kwargs={"normalize_embeddings": True}, # 向量归一化
),
)
# 将向量存储转换为检索器
retriever = vectorstore.as_retriever()
# 创建一个聊天提示模板,用中文设置模板以便生成基于特定上下文和问题的完整输入
template = """根据以下上下文回答问题:
{context}
问题: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 初始化输出解析器,将模型输出转换为字符串
output_parser = StrOutputParser()
# 设置一个并行运行器,用于同时处理上下文检索和问题传递
# 使用RunnableParallel来准备预期的输入,通过使用检索到的文档条目以及原始用户问题,
# 利用文档搜索器 retriever 进行文档搜索,并使用 RunnablePassthrough 来传递用户的问题。
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
# 构建一个处理链,包括上下文和问题的设置、提示生成、模型调用和输出解析
chain = setup_and_retrieval | prompt | model | output_parser
# 调用处理链,传入问题"where did harrison work?"(需翻译为中文),并基于给定的文本上下文生成答案
chain.invoke("harrison在哪里工作?") # '根据提供的文档内容,可以直接回答问题:\n\n**Harrison 在 Kensho 工作。**\n\n**依据:**\n* 文档中明确说明:`harrison worked at kensho` (Harrison 在 Kensho 工作)。\n* 另一个文档 (`bears like to eat h忽略警告提示:
UserWarning:
pydantic.error_wrappers:ValidationError
has been moved topydantic:ValidationError
.原因:
The issue is with pydantic version, it's 2.0.0+ and not compatible with docarray. Instead it should be pydantic==1.10.9
参考:https://github.com/langchain-ai/langchain/issues/15394
LangChain官方关于 Pydatic 兼容性的说明:https://python.langchain.com/v0.1/docs/guides/development/pydantic_compatibility/
- 首先创建一个包含两个条目(entries)的
7.4 Runnable Interface
为了尽可能简化创建自定义链的过程,Langchain 实现了一个 Runnable 协议。
许多 LangChain 组件都实现了 Runnable 协议,包括 chat models, LLMs, output parsers, retrievers, prompt templates等等。此外,还有几个用于处理可运行对象的有用原语。
Runnable 是一个标准接口,包括:
- stream:流式返回生成内容(chunk)
- invoke:对输入调用该链
- batch:对输入列表调用该链
不同组件的输入和输出类型有所差异:
组件 输入类型 输出类型 Prompt Dictionary PromptValue ChatModel Single string, list of chat messages or a PromptValue ChatMessage LLM Single string, list of chat messages or a PromptValue String OutputParser The output of an LLM or ChatModel Depends on the parser Retriever Single string List of Documents Tool Single string or dictionary, depending on the tool Depends on the tool 所有 Runnable 对象都显式描述输入和输出 Schema,以检查输入和输出格式:
- input_schema:从Runnable的结构自动生成的输入Pydantic模型
- output_schema:从Runnable的结构自动生成的输出Pydantic模型
7.4.1 Input Schema
为了演示如何使用,下面我们创建一个超级简单的PromptTemplate + ChatModel链。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# from langchain_openai import ChatOpenAI
from langchain_deepseek import ChatDeepSeek
from langchain_core.prompts import ChatPromptTemplate
# model = ChatOpenAI(model="gpt-4o-mini")
model = ChatDeepSeek(model="deepseek-reasoner", temperature=0)
prompt = ChatPromptTemplate.from_template("讲个关于 {topic} 的笑话吧")
chain = prompt | model
# schema 方法:一个描述 Runnable 接受的输入的说明。这是根据任何 Runnable 结构动态生成的 Pydantic 模型。您可以调用 .schema() 来获取 JSONSchema 表示。
# 查看 Chain 的输入类型
chain.input_schema.model_json_schema()
# {'properties': {'topic': {'title': 'Topic', 'type': 'string'}},
'required': ['topic'],
'title': 'PromptInput',
'type': 'object'}
# 查看 Prompt 的输入类型(Chain的输入从 Prompt 开始,因此输入类型一致)
prompt.input_schema.schema()
# {'properties': {'topic': {'title': 'Topic', 'type': 'string'}},
'required': ['topic'],
'title': 'PromptInput',
'type': 'object'}
# 查看 Model 的输入类型
model.input_schema.model_json_schema()
7.4.2 Output Schema
输出类型仍然可以调用 .schema() 来获取其
JSONSchema
表示。1
2# 查看 Chain 的输出类型
chain.output_schema.schema()
7.4.3 Stream
使用 .stream() 方法查看(同步)流式输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14for s in chain.stream({"topic": "程序员"}):
print(s.content, end="", flush=True)
# 当然可以!这里有一个关于程序员的笑话:
#
# 有一天,一位程序员走进了酒吧,点了一杯酒。酒保问他:“你想要加冰吗?”
#
# 程序员回答:“不,我只想要一个整数。”
#
# 酒保困惑地问:“什么是一个整数?”
#
# 程序员微笑着说:“就是不加冰的。”
#
# 希望这个笑话能让你笑一笑!
7.4.4 Invoke
使用 .invoke() 方法单次(同步)调用。
1
chain.invoke({"topic": "程序员"}) # AIMessage(content='当然可以!这是一个关于程序员的笑话:\n\n有一天,一个程序员走进一家咖啡馆,点了一杯咖啡。服务员问他:“要加糖吗?”\n\n程序员回答说:“不,我只想要一个‘0’或‘1’。”\n\n服务员困惑地问:“这是什么意思?”\n\n程序员笑着说:“因为我只需要‘甜’或者‘不甜’!” \n\n希望这个笑话能让你笑一笑!', response_metadata={'token_usage': {'completion_tokens': 102, 'prompt_tokens': 18, 'total_tokens': 120}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_9722793223', 'finish_reason': 'stop', 'logprobs': None}, id='run-e0cc952f-47ee-49c1-82c4-711145e26be2-0')
7.4.5 Batch
使用 .batch() 方法(同步)批量调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58chain.batch([{"topic": "程序员"}, {"topic": "产品经理"}, {"topic": "测试经理"}])
# [AIMessage(content='当然可以!这里有一个关于程序员的笑话:\n\n有一天,程序员走进一家酒吧,点了一杯啤酒。 bartender问他:“你要喝什么类型的啤酒?”程序员回答:“我想要一杯‘Bug-Free’的啤酒!” bartender愣了一下,问道:“那是什么样的啤酒?”程序员笑着说:“当然是一种不会出现任何问题的啤酒,但我知道这绝对不可能!”\n\n希望你喜欢这个笑话!', response_metadata={'token_usage': {'completion_tokens': 113, 'prompt_tokens': 18, 'total_tokens': 131}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_9722793223', 'finish_reason': 'stop', 'logprobs': None}, id='run-de7e977b-ec6a-4e62-bb91-40d8b6d76fa2-0'),
AIMessage(content='当然可以!这里有一个关于产品经理的笑话:\n\n有一天,产品经理、开发者和设计师一起去参加一个团队建设活动。在活动中,他们需要一起搭建一个帐篷。\n\n产品经理说:“我们需要先讨论一下用户需求,确保帐篷能满足所有用户的期望。”\n\n开发者说:“好吧,但我们必须先确定技术可行性,看看能不能在这个预算内搭建。”\n\n设计师则说:“等等,我觉得我们应该先选一个更好看的颜色!”\n\n最后,帐篷还是没搭好,但他们却在讨论中达成了共识:下次一定要先搭建一个“最小可行产品”! \n\n希望你喜欢这个笑话!', response_metadata={'token_usage': {'completion_tokens': 157, 'prompt_tokens': 16, 'total_tokens': 173}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_5bd87c427a', 'finish_reason': 'stop', 'logprobs': None}, id='run-7267180f-a443-4979-a0a7-f3d5ae703043-0'),
AIMessage(content='当然可以!这里有一个关于测试经理的笑话:\n\n有一天,一个测试经理走进咖啡店,看到菜单上写着“咖啡 - 5元”。他立刻对服务员说:“请给我一个咖啡!”\n\n服务员问:“要加糖吗?”\n\n测试经理回答:“请给我一个没有糖的咖啡。”\n\n服务员又问:“要加奶吗?”\n\n测试经理严肃地说:“请给我一个没有奶的咖啡。”\n\n服务员有些困惑,问:“那您要什么呢?”\n\n测试经理微笑着回答:“我只想要一个通过测试的咖啡!”\n\n希望这个笑话能让你开心!', response_metadata={'token_usage': {'completion_tokens': 150, 'prompt_tokens': 18, 'total_tokens': 168}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_f33667828e', 'finish_reason': 'stop', 'logprobs': None}, id='run-5d01e9c0-223d-4d45-9d4e-cf2cdfae2f89-0')]
messages = chain.batch([{"topic": "程序员"}, {"topic": "产品经理"}, {"topic": "测试经理"}])
# 使用 StrOutputParser 来处理 Batch 批量输出
from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()
for idx, m in enumerate(messages):
print(f"笑话{idx}:\n")
print(output_parser.invoke(m))
print("\n")
# 笑话0:
#
# 当然可以!这是一个关于程序员的笑话:
#
# 有一天,一个程序员走进酒吧,看到了一张空桌子。他对酒保说:“请给我一杯水。”
# 酒保点点头,转身去倒水。
# 程序员等了好久,酒保还是没有回来。于是他又说:“请再给我一杯水。”
# 酒保依然没有回应。
# 最后,程序员忍不住了,站起来大喊:“如果你不来,我就要自己写一个水的获取函数!”
#
# 希望这个笑话能让你笑一笑!
#
#
# 笑话1:
#
# 当然可以!这是一个关于产品经理的笑话:
#
# 有一天,产品经理、开发人员和设计师一起坐在会议室里讨论新产品的功能。
#
# 产品经理说:“我们需要添加一个用户反馈的功能,这样我们可以收集用户的意见。”
#
# 开发人员皱了皱眉头:“可是这会增加我们的开发时间。”
#
# 设计师插嘴说:“我觉得用户根本不知道他们想要什么。”
#
# 产品经理微笑着说:“没关系,我们可以先把这个功能做出来,然后再问他们。”
#
# 开发人员和设计师对视一眼,异口同声地说:“这听起来像是你们的工作!”
#
# 希望这个笑话让你开心!
#
#
# 笑话2:
#
# 当然可以!这里有一个关于测试经理的笑话:
#
# 有一天,一位测试经理走进咖啡店,点了一杯咖啡。咖啡师问他:“您想要加糖吗?”
#
# 测试经理回答:“请先给我一杯不加糖的咖啡,然后我会测试一下加糖的效果!”
#
# 咖啡师无奈地摇了摇头:“您真是个彻底的测试经理,连喝咖啡也要进行AB测试!”
#
# 希望这个笑话能让你开心!
7.4.6 异步操作
- 这些方法也有相应的异步方法,应与
asyncio
的await
语法一起使用以进行并发操作:- astream:异步地流式返回生成内容(chunk)
- ainvoke:异步地对输入调用该链
- abatch:异步地对输入列表调用该链
- astream_log: 在发生时会返回中间步骤,并且最终返回结果之外。
- astream_events: beta 流式传输事件,在 langchain-core 0.1.14 中引入
7.4.6.1 Async Stream
1 | async for s in chain.astream({"topic": "程序员"}): |
7.4.6.2 Async Invoke
1 | await chain.ainvoke({"topic": "程序员"}) |
7.4.6.3 Async Batch
1 | await chain.abatch([{"topic": "程序员"}, {"topic": "产品经理"}, {"topic": "测试经理"}]) |
7.5 Multiple Chain
Runnables 可以轻松地用来串联多个 Chains,使用 RunnablePassthrough 将输出同时传给多条后继链。
本指南展示如何使用 Runnable 实现多个 AI 关于相同话题的辩论:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63# 导入相关模块,包括运算符、输出解析器、聊天模板、ChatOpenAI 和 运行器
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
# 创建一个计划器,生成一个关于给定输入的论证
planner = (
ChatPromptTemplate.from_template("生成关于以下内容的论点: {input}")
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
| {"base_response": RunnablePassthrough()}
)
# 创建正面论证的处理链,列出关于基础回应的正面或有利的方面
arguments_for = (
ChatPromptTemplate.from_template(
"列出关于{base_response}的正面或有利的方面"
)
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
# 创建反面论证的处理链,列出关于基础回应的反面或不利的方面
arguments_against = (
ChatPromptTemplate.from_template(
"列出关于{base_response}的反面或不利的方面"
)
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
# 创建最终响应者,综合原始回应和正反论点生成最终的回应
final_responder = (
ChatPromptTemplate.from_messages(
[
("ai", "{original_response}"),
("human", "正面观点:\n{results_1}\n\n反面观点:\n{results_2}"),
("system", "给出批评后生成最终回应"),
]
)
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
# 构建完整的处理链,从生成论点到列出正反论点,再到生成最终回应
chain = (
planner
| {
"results_1": arguments_for,
"results_2": arguments_against,
"original_response": itemgetter("base_response"),
}
| final_responder
)
print(chain.invoke({"input": "房地产低迷"}))
# 流式输出
## chain 最终输出经过了 StrOutputParser 处理,所以可以直接输出流式输出 s
for s in chain.stream({"input": "全球经济"}):
print(s, end="", flush=True)