赞
踩
任何语言模型应用程序的核心元素是什么?LangChain 提供了与任何语言模型交互的构建块。
语言模型的提示是由用户提供的一组指令或输入,用于指导模型的响应,帮助它理解上下文并生成相关且一致的基于语言的输出,例如回答问题、完成句子或参与对话。LangChain 提供了几个类和函数来帮助构造和处理提示。
参数化的提示模板,是一些预定义的片段用于生成模型所需的提示词,模板可能包括说明、少量示例以及适合给定任务的特定上下文和问题。LangChain 试图创建与模型无关的模板,以便在不同的语言模型之间轻松重用现有模板。
from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template("Tell me a {adjective} joke about {content}.")
prompt_template.format(adjective="funny", content="chickens")
连接特征库以针对不同用户提供个性化服务
class FeastPromptTemplate(StringPromptTemplate): def format(self, **kwargs) -> str: driver_id = kwargs.pop("driver_id") feature_vector = store.get_online_features( features=[ "driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips", ], entity_rows=[{"driver_id": driver_id}], ).to_dict() kwargs["conv_rate"] = feature_vector["conv_rate"][0] kwargs["acc_rate"] = feature_vector["acc_rate"][0] kwargs["avg_daily_trips"] = feature_vector["avg_daily_trips"][0] return prompt.format(**kwargs) prompt_template = FeastPromptTemplate(input_variables=["driver_id"])
自定义提示词模板,有两种不同的提示模板——字符串提示模板和聊天提示模板。字符串提示模板提供字符串格式的简单提示,而聊天提示模板生成更结构化的提示,用于聊天API。主要实现以下两方面:
class FunctionExplainerPromptTemplate(StringPromptTemplate, BaseModel): """函数解释提示词模板""" @validator("input_variables") def validate_input_variables(cls, v): """校验参数""" if len(v) != 1 or "function_name" not in v: raise ValueError("function_name must be the only input_variable.") return v def format(self, **kwargs) -> str: # 获取函数体源码 source_code = get_source_code(kwargs["function_name"]) # 组装提示词 prompt = PROMPT.format( function_name=kwargs["function_name"].__name__, source_code=source_code ) return prompt
少样本提示词模板,可以从一组示例或从一个示例选择器对象构造一个简短的提示模板。
# 从一组示例创建少样本提示词
examples = [{
"question": "Who lived longer, Muhammad Ali or Alan Turing?",
"answer": "..."
},{...}]
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="Question: {question}\n{answer}")
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="Question: {input}", # 添加最后问题模板
input_variables=["input"]
)
# 调用
print(prompt.format(input="Who was the father of Mary Ball Washington?"))
局部初始化提示词模板
prompt = PromptTemplate(template="{foo}{bar}", input_variables=["foo", "bar"])
partial_prompt = prompt.partial(foo="foo");
print(partial_prompt.format(bar="baz")) # 输出: foobaz
# 方式2:使用 partial_variables
prompt = PromptTemplate(template="{foo}{bar}", input_variables=["bar"], partial_variables={"foo": "foo"})
print(prompt.format(bar="baz"))
# 通过函数局部初始化
prompt = PromptTemplate(template="Tell me a {adjective} joke about the day {date}",
input_variables=["adjective", "date"]);
partial_prompt = prompt.partial(date=_get_datetime)
print(partial_prompt.format(adjective="funny"))
组合模板
input_prompts = [("introduction", introduction_prompt),
("example", example_prompt),
("start", start_prompt)]
pipeline_prompt = PipelinePromptTemplate(final_prompt=full_prompt, pipeline_prompts=input_prompts)
序列化,支持从文件中加载模板,同时支持 JSON 和 YAML 格式,支持在一个文件中指定所有内容,或者在不同的文件中存储不同的组件 (模板、示例等) 并引用它们
{
"_type": "prompt",
"input_variables": ["adjective", "content"],
"template": "Tell me a {adjective} joke about {content}."
}
prompt = load_prompt("simple_prompt.json")
print(prompt.format(adjective="funny", content="chickens"))
字符串提示词模板流水线化
prompt = (
PromptTemplate.from_template("Tell me a joke about {topic}") # 第一个元素需要是提示词模板类型
+ ", make it funny"
+ "\n\nand in {language}"
)
如果您有大量的示例,您可能需要动态选择在提示符中包含哪些示例。示例选择器是实现该功能的类。它需要定义的唯一方法是 select_examples 方法。它接受输入变量,然后返回一个示例列表。如何选择这些示例取决于每个具体实现。内置的选择器有长度选择器、相关性选择器、n-gram overlap 选择器、相似度选择器等
class BaseExampleSelector(ABC):
"""Interface for selecting examples to include in prompts."""
@abstractmethod
def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
"""Select which examples to use based on the inputs."""
LangChain 没有自己的语言模式,而是提供了一个标准接口与许多不同的大模型进行交互。主要为两类模型提供接口和集成:
LangChain 中的 LLM 指的是纯文本补全模型。它们的 api 接受字符串提示符作为输入,输出字符串补全。聊天模型通常由 LLMs 支持,但专门针对对话进行了调整。重要的是,它们的提供的 api 使用与纯文本补全模型不同的接口。它们将聊天消息列表作为输入,而不是单个字符串。通常,这些信息都标有角色 (通常是“system”,“ai” 和 “human”), 会返回一条聊天信息作为输出。GPT-4 和 Anthropic 的 Claude 都是作为聊天模型实现的。
为了能够兼容 LLMs 和聊天模型,两者都实现了 Base Language Model 接口。这包括常见的方法 “predict”,它接受一个字符串并返回一个字符串,以及“predict messages”,它接受一个消息并返回一个消息。如果正在使用特定的模型,建议使用特定于该模型的类的方法。
responses = ["Action: ...", "Final Answer: ..."]
llm = FakeListLLM(responses=responses)
语言模型输出文本。但很多时候,你可能想要得到更多结构化的信息,而不仅仅是短信回复。这就是输出解析器的用武之地。输出解析器需要实现的方法:
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}
)
model = OpenAI(temperature=0)
_input = prompt.format(subject="ice cream flavors")
output = model(_input)
output_parser.parse(output)
许多 LLM 应用需要使用不属于模型训练集的用户特定数据。实现这一目标的主要方法是通过检索增强生成(RAG)。在这个过程中,外部数据被检索,然后在执行生成步骤时作为补充内容传递给 LLM。LangChain 为 RAG 应用程序提供了从简单到复杂的所有构建块。
最简单的加载器将文件作为文本读入,并将其全部放入一个文档中。
from langchain.document_loaders import TextLoader
loader = TextLoader("./index.md")
loader.load()
loader = DirectoryLoader('../', glob="**/*.md", loader_cls=TextLoader)
docs = loader.load()
loader = UnstructuredHTMLLoader("example_data/fake-content.html")
loader = BSHTMLLoader("example_data/fake-content.html")
loader = UnstructuredMarkdownLoader(markdown_path)
loader = PyPDFLoader("example_data/layout-parser-paper.pdf")
pages = loader.load_and_split()
文本切片,当您想要处理长文本时,有必要将该文本分割成块。虽然这听起来很简单,但这里有很多潜在的复杂性。理想情况下,您希望将语义相关的文本片段保持在一起。文本分割器的工作方式如下:
默认推荐的文本分割器是 RecursiveCharacterTextSplitter。这个文本分割器接受一个字符列表。它尝试基于分割第一个字符创建块,但如果任何块太大,它就会移动到下一个字符,以此类推。
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 100, # Set a really small chunk size, just to show.
chunk_overlap = 20,
length_function = len,
add_start_index = True,
)
headers_to_split_on = [ ("h1", "Header 1"), ("h2", "Header 2"), ("h3", "Header 3"), ] html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on) html_header_splits = html_splitter.split_text(html_string) # 使用其他文本分割器进一步处理 chunk_size = 500 chunk_overlap = 30 text_splitter = RecursiveCharacterTextSplitter( chunk_size=chunk_size, chunk_overlap=chunk_overlap ) splits = text_splitter.split_documents(html_header_splits)
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size=100, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)
Embeddings 类是一个设计用于与文本嵌入模型交互的类。有很多嵌入模型提供程序(OpenAI, Cohere, Hugging Face 等),这个类的目的是为它们提供一个标准接口。嵌入创建一段文本的向量表示,这意味着我们可以考虑向量空间中的文本,并进行语义搜索,在向量空间中寻找最相似的文本片段。LangChain 中的 Embeddings 类提供了两个方法: 一个用于嵌入文档,另一个用于嵌入查询。
from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()
embeddings = embeddings_model.embed_documents(["Hi there!", "Oh, hello!", ...])
# 嵌入查询
embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")
向量数据库负责存储嵌入的数据并为您执行向量搜索。
from langchain.document_loaders import TextLoader from langchain.embeddings.openai import OpenAIEmbeddings from langchain.text_splitter import CharacterTextSplitter from langchain.vectorstores import Chroma # 加载文档, 切分成文本块, 嵌入为向量存储到数据库 raw_documents = TextLoader('../../../state_of_the_union.txt').load() text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) documents = text_splitter.split_documents(raw_documents) db = Chroma.from_documents(documents, OpenAIEmbeddings()) # 相似搜索 query = "What did the president say about Ketanji Brown Jackson" docs = db.similarity_search(query) print(docs[0].page_content) # 通过向量搜索 embedding_vector = OpenAIEmbeddings().embed_query(query) docs = db.similarity_search_by_vector(embedding_vector)
检索器是一个接口,它在给定非结构化查询时返回文档。它比向量数据库更通用。检索器不需要能存储文档,只需要能够返回(或检索)它们。向量数据库可以用作检索器的核心,但也有其他类型的检索器。
question = "What are the approaches to Task Decomposition?"
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectordb.as_retriever(), llm=llm
)
unique_docs = retriever_from_llm.get_relevant_documents(query=question)
from langchain.llms import OpenAI
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
llm = OpenAI(temperature=0)
# LLMChainExtractor,它将遍历最初返回的文档,并仅从每个文档中提取与查询相关的内容。
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=retriever)
compressed_docs = compression_retriever.get_relevant_documents("What did the president say...")
pretty_print_docs(compressed_docs)
# initialize the bm25 retriever and faiss retriever
bm25_retriever = BM25Retriever.from_texts(doc_list)
bm25_retriever.k = 2
embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(doc_list, embedding)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})
# initialize the ensemble retriever
ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5])
ParentDocumentRetriever
通过分割和存储小块数据来实现这种平衡。在检索期间,它首先获取小块,然后查找这些块的父 id 并返回较大的文档。“父文档”指的是小块源自的文档。这可以是整个原始文档,也可以是更大的块。child_splitter = RecursiveCharacterTextSplitter(chunk_size=400) # The vectorstore to use to index the child chunks vectorstore = Chroma( collection_name="full_documents", embedding_function=OpenAIEmbeddings() ) # The storage layer for the parent documents store = InMemoryStore() retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=store, child_splitter=child_splitter, ) retriever.add_documents(docs, ids=None) # 向量数据库中可检索小文本块 sub_docs = vectorstore.similarity_search("justice breyer") # 返回较小块所在的文档 retrieved_docs = retriever.get_relevant_documents("justice breyer")
semantic_similarity + (1.0 - decay_rate) ^ hours_passed
, hours_passed 指的是自上次访问检索器中的对象以来经过的小时数,而不是自创建对象以来经过的小时数。这意味着频繁访问的对象会保持“新鲜”。索引 API 允许您将来自任何源的文档加载到向量数据库中并保持同步。具体来说,它有助于:
LangChain 索引使用记录管理器 (RecordManager) 来跟踪写入向量数据库的文档。当索引内容时,为每个文档计算哈希值,并将以下信息存储在记录管理器中:
collection_name = "test_index" embedding = OpenAIEmbeddings() vectorstore = ElasticsearchStore(es_url="http://localhost:9200", index_name="test_index", embedding=embedding) namespace = f"elasticsearch/{collection_name}" record_manager = SQLRecordManager(namespace, db_url="sqlite:///record_manager_cache.sql") # 在使用记录管理器之前创建 schema。 record_manager.create_schema() # 待创建索引的测试文档 doc1 = Document(page_content="kitty", metadata={"source": "kitty.txt"}) doc2 = Document(page_content="doggy", metadata={"source": "doggy.txt"}) def _clear(): """清理""" index([], record_manager, vectorstore, cleanup="full", source_id_key="source") # 建立索引 index( [doc1, doc1, doc1, doc1, doc1], record_manager, vectorstore, cleanup=None, source_id_key="source", )
对于简单的应用程序,单独使用 LLM 是可以的,但是更复杂的应用程序需要将 LLMs 链接起来——要么相互链接,要么与其他组件链接。
LangChain 提供了两个用于“链接”组件的高级框架。旧方法是使用 Chain 接口。更新的推荐方法是使用 LangChain 表达式语言(LCEL)。但是有许多有用的、内置的链,将继续支持,Chain 本身也可以在LCEL中使用,因此两者并不相互排斥。
LCEL最明显的部分是它为组合提供了直观和可读的语法。但更重要的是,它还提供了以下支持: 流, 异步调用, 批处理, 并行化, 重试, 回退, 跟踪和更多。
一个简单而常见的例子是将提示符、模型和输出解析器组合在一起:
from langchain.chat_models import ChatAnthropic
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
model = ChatAnthropic()
prompt = ChatPromptTemplate.from_messages([
("system", "You're a very knowledgeable historian who provides ..."),
("human", "{question}"),
])
runnable = prompt | model | StrOutputParser()
for chunk in runnable.stream({"question": "How did Mansa Musa accumulate his wealth?"}):
print(chunk, end="", flush=True)
from langchain.chains import LLMChain
chain = LLMChain(llm=model, prompt=prompt, output_parser=StrOutputParser())
chain.run(question="How did Mansa Musa accumulate his wealth?")
LLMChain 是一个简单的链,语言模型增加功能。它在整个 LangChain 中广泛使用,包括在其他链和代理中。它由 PromptTemplate 和语言模型组成。它使用输入参数或使用内存键值对)格式化提示词模板,将格式化后的字符串传递给 LLM,并返回 LLM 输出。
除了所有 Chain 对象都有的 __call__
和 run
方法外,它还提供了其他几个方法:
路由链,它可以动态地根据输入选择下一个链。
MultiPromptChain 创建一个问答路由链,该链选择与给定问题最相关的提示,然后使用该提示回答问题。
physics_template = """You are a very smart physics professor. ...Here is a question: {input}""" math_template = """You are a very good mathematician. ...Here is a question: {input}""" prompt_infos = [{ "name": "physics", "description": "Good for answering questions about physics", "prompt_template": physics_template, }, { "name": "math", "description": "Good for answering math questions", "prompt_template": math_template, }] # 使用 openai 的模型 llm = OpenAI() # 每个提示词模板生成一个 destination_chain destination_chains = {} for p_info in prompt_infos: name = p_info["name"] prompt_template = p_info["prompt_template"] prompt = PromptTemplate(template=prompt_template, input_variables=["input"]) chain = LLMChain(llm=llm, prompt=prompt) destination_chains[name] = chain default_chain = ConversationChain(llm=llm, output_key="text") # 使用内置模板 MULTI_PROMPT_ROUTER_TEMPLATE 创建路由链 destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] destinations_str = "\n".join(destinations) router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str) router_prompt = PromptTemplate( template=router_template, input_variables=["input"], output_parser=RouterOutputParser(), ) router_chain = LLMRouterChain.from_llm(llm, router_prompt) # 创建多模板路由链 chain = MultiPromptChain( router_chain=router_chain, destination_chains=destination_chains, default_chain=default_chain, verbose=True, ) print(chain.run("What is black body radiation?"))
对语言模型进行一连串调用,从一个调用中获取输出并将其作为另一个调用的输入。顺序链允许您连接多个链,并将它们组合成执行某些特定场景的管道。有两种类型的顺序链:
# 传递单字符串作为参数,所有步骤的输出也是单个字符串
overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)
review = overall_chain.run("Tragedy at sunset on the beach")
overall_chain = SequentialChain(
chains=[synopsis_chain, review_chain],
input_variables=["era", "title"],
# Here we return multiple variables
output_variables=["synopsis", "review"],
verbose=True)
例如,将文本过滤到仅前 3 段,然后将其传递到 LLMChain 中以总结这些文本
def transform_func(inputs: dict) -> dict:
text = inputs["text"]
shortened_text = "\n\n".join(text.split("\n\n")[:3])
return {"output_text": shortened_text}
transform_chain = TransformChain(
input_variables=["text"], output_variables=["output_text"], transform=transform_func
)
template = """Summarize this text:
{output_text}
Summary:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
sequential_chain.run("...")
大多数 LLM 应用程序都有会话功能。对话的一个重要组成部分是能够引用对话之前介绍的信息。至少,会话系统应该能够直接访问过去的某些对话消息。更复杂的系统需要有一个不断更新的世界模型,使它有能力做一些其他事情,比如维护实体及其关系的信息。
把这种储存过去互动信息的能力称为“记忆”。LangChain 提供了许多用于向系统添加记忆的实用工具。这些工具可以单独使用,也可以无缝地添加到一个链中。
存储: 聊天消息列表
任何记忆的底层都是所有聊天交互的历史记录。即使不直接使用它们,也需要以某种形式存储它们。LangChain 记忆模块的关键部分之一是用于存储这些聊天消息的一系列集成,从内存列表到持久数据库。
查询: 聊天消息的数据结构和算法
建立在聊天消息之上的数据结构和算法,为这些消息提供最有用的视图。一个非常简单的记忆系统每次运行时可能只返回最近的消息。稍微复杂一点的可能会返回过去多条消息的摘要。更复杂的系统可能会从存储的消息中提取实体,并且只返回引用的实体的信息。
有许多不同类型的记忆工具,每一种都有自己的参数及返回类型,适用不同的场景。
llm = OpenAI(temperature=0)
# 其中模板参数 chat_history 来自记忆模块
template = """You are a nice chatbot having a conversation with a human.
Previous conversation:
{chat_history}
New human question: {question}
Response:"""
prompt = PromptTemplate.from_template(template)
# 设置 `memory_key` 与模板一致
memory = ConversationBufferMemory(memory_key="chat_history")
conversation = LLMChain(llm=llm, prompt=prompt, verbose=True, memory=memory)
# 只需要设置 `question` 变量,`chat_history` 从记忆模块提取
conversation({"question": "hi"})
在 LangChain 中有一些预定义的记忆类型,还可以创建适合自己的应用程序的记忆类。下例中,将演示如何编写一个自定义内存类,它使用 spaCy 提取实体,并将有关实体的信息保存在一个简单的散列表中。然后,在对话期间,我们将根据输入提取实体,并将信息放入上下文中。
import spacy # 加载英文模型, 以进行分词 nlp = spacy.load("en_core_web_lg") class SpacyEntityMemory(BaseMemory, BaseModel): """自定义记忆类,用于存储实体信息""" # 存储实体信息的字典 entities: dict = {} # 定义键以将有关实体的信息传递到提示词模板 memory_key: str = "entities" def clear(self): self.entities = {} @property def memory_variables(self) -> List[str]: return [self.memory_key] def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]: """加载内存实体信息""" # 通过 spaCy 从输入文本获取实体 doc = nlp(inputs[list(inputs.keys())[0]]) # 提取已知的实体信息 entities = [ self.entities[str(ent)] for ent in doc.ents if str(ent) in self.entities ] # 返回拼接的实体信息 return {self.memory_key: "\n".join(entities)} def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: """存储记忆到记忆缓存""" text = inputs[list(inputs.keys())[0]] doc = nlp(text) # 针对每个实体,存储信息到字典中 for ent in doc.ents: ent_str = str(ent) if ent_str in self.entities: # 已存在则拼接 self.entities[ent_str] += f"\n{text}" else: self.entities[ent_str] = text
代理的核心思想是使用 LLM 来选择要采取的一系列行动。在链中,一系列动作是硬编码的(用代码)。在代理中,语言模型被用作推理引擎,以确定以何种顺序采取哪些操作。
tool
属性 (被调用的工具的名称) 和一个tool_input
属性(工具的输入)output
-是一个字符串,通常只是返回这个键。List[Tuple[AgentAction, Any]]
。请注意,观测值目前保留为 Any 类型,以获得最大的灵活性。在实践中通常是一个字符串。下面介绍该模块的几个主要组件:
这是负责决定下一步采取什么步骤的 Chain,基于语言模型和提示词来实现。这个链的输入是:
然后,该链返回要采取的下一个操作或要发送给用户的最终响应 (AgentAction 或 AgentFinish)。不同的代理具有不同的推理提示词风格、输入编码方式和输出解析方式。
工具是 Agent 调用的函数,一是让代理可以访问正确的工具,二是以对代理最有帮助的方式描述工具。LangChain提供了一套广泛的工具直接使用,同时也可以很容易地定义自己的工具(包括自定义描述)。
在构造代理时,需要为它提供一个工具列表。除了实际调用的函数外,工具由以下几个组件组成:
为了更容易定义自定义工具,提供了 @tool 装饰器。这个装饰器可以用来从一个简单的函数快速创建一个工具。装饰器默认使用函数名作为工具名,但可以通过传递一个字符串作为第一个参数来覆盖这一点。此外,装饰器将使用函数的注释作为工具的描述。
from langchain.tools import tool
@tool("search", return_direct=True, args_schema=SearchInput)
def search_api(query: str) -> str:
"""Searches the API for the query."""
return f"Results for query {query}"
通常,代理可以访问的工具集比单个工具更重要。为此,LangChain提供了工具包的概念——完成特定目标所需的工具包,一个工具包中通常有3-5个工具。
代理执行器是代理的运行时, 调用代理并执行它选择的操作。为您处理一些复杂的问题: 代理选择了不存在的工具,工具出错的情况,代理产生的输出无法解析为工具调用的情况,所有级别(代理决策、工具调用)的日志记录和可观察性。
构造一个可以访问自定义工具的自定义代理。在这个例子中展示如何使用 LCEL(LangChain Expression Language) 从头构建这个代理,这个代理基于 OpenAI 的函数调用来实现,给 Agent 的工具是一个计算单词长度的工具,并展示如何结合记忆模块。
from langchain.agents import tool
from langchain.chat_models import ChatOpenAI
# 基于 openai 的对话模型
llm = ChatOpenAI(temperature=0)
# 定义一个计算字符串长度的工具
@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
tools = [get_word_length]
创建提示词模板。因为 OpenAI 的函数调用针对工具使用进行了微调,所以我们几乎不需要任何关于如何推理或如何输出格式的说明。只有两个输入变量: input(用户问题) 和 agent_scratchpad (之前采取的步骤)。
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.schema.messages import HumanMessage, AIMessage from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser MEMORY_KEY = "chat_history" prompt = ChatPromptTemplate.from_messages([ ("system", "You are very powerful assistant, but bad at calculating lengths of words."), MessagesPlaceholder(variable_name=MEMORY_KEY), ("user", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ]) # 将工具绑定到到 LLM from langchain.tools.render import format_tool_to_openai_function llm_with_tools = llm.bind( functions=[format_tool_to_openai_function(t) for t in tools] ) # 组装 Agent chat_history = [] agent = { # 模板参数 'input' "input": lambda x: x["input"], # 表示先前的代理操作和相应输出 "agent_scratchpad": lambda x: format_to_openai_functions(x['intermediate_steps']) "chat_history": lambda x: x["chat_history"] # Parser 将输出转换为 AgentAction 或 AgentFinish。 } | prompt | llm_with_tools | OpenAIFunctionsAgentOutputParser()
创建好 Agent 后的调用流程:
intermediate_steps = [] # 中间步骤 while True: output = agent.invoke({ "input": "how many letters in the word educa?", "intermediate_steps": intermediate_steps }) if isinstance(output, AgentFinish): # 完成 final_result = output.return_values["output"] break else: # 动作执行函数调用 print(output.tool, output.tool_input) tool = { "get_word_length": get_word_length }[output.tool] observation = tool.run(output.tool_input) intermediate_steps.append((output, observation)) print(final_result)
可以使用 AgentExecutor 类简化上面的调用流程。它将上述所有功能捆绑在一起,并添加了错误处理、提前终止、跟踪和其他改进,从而减少了需要编写的保护逻辑。
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
input1 = "how many letters in the word educa?"
result = agent_executor.invoke({"input": input1})
chat_history.append(HumanMessage(content=input1)) # 保存该轮对话记忆
chat_history.append(AIMessage(content=result['output']))
# 第二轮对话
agent_executor.invoke({"input": "is that a real word?", "chat_history": chat_history})
LangChain 提供了一个回调系统,允许添加钩子到 LLM 应用程序的各个阶段。这对于日志记录、监视、流处理和其他任务非常有用。
Callback Handlers 是实现了 CallbackHandler 接口的对象,该接口为每个可以订阅的事件提供一个方法。当事件被触发时,CallbackManager 将调用对应的方法。需要实现下面的一个或多个方法:
class BaseCallbackHandler: """Base callback handler that can be used to handle callbacks from langchain.""" def on_llm_start() def on_chat_model_start() def on_llm_new_token() def on_llm_end() def on_llm_error() def on_chain_start() def on_chain_end() def on_chain_error() def on_tool_start() def on_tool_end() def on_tool_error() def on_text() -> Any: def on_agent_action() def on_agent_finish()
自定义回调处理器:
class MyCustomHandler(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs) -> None:
print(f"My custom handler, token: {token}")
LangChain 提供了一些内置处理器(langchain/callbacks) 。最基本的一个处理器是StdOutCallbackHandler,它将所有事件记录到标准输出。当 verbose
设置为 true 时,即使没有显式传递,也会调用 StdOutCallbackHandler。callbacks
参数在 API 中的大多数对象(链、模型、工具、代理等)上都是可用的。
handler = StdOutCallbackHandler()
llm = OpenAI()
prompt = PromptTemplate.from_template("1 + {number} = ")
# 构造函数设置回调: 在初始化链时显式设置 StdOutCallbackHandler
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])
chain.run(number=2)
# 使用 verbose 标志来达到相同的效果,适用于记录所有请求
chain = LLMChain(llm=llm, prompt=prompt, verbose=True)
chain.run(number=2)
# 使用请求回调来实现相同的效果, 想要将单个特定的 websocket 连接请求流式输出
chain = LLMChain(llm=llm, prompt=prompt)
chain.run(number=2, callbacks=[handler])
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。