学习笔记(一)——Langchain基本操作与函数
目录
- 学习笔记(一)——Langchain基本操作与函数
- 基本初始化配置
- Langsmith
- Language Models
- 基础指令
- 传递信息
- OutputParsers 输出解析器
- chain 链
- Prompt Templates 提示模板
- Message History 消息历史记录
- Managing Conversation History 管理对话历史记录
- Streaming 流
- Vector stores and retrievers 矢量存储和检索器
- 文档document
- Vector stores 矢量存储
- Retrievers
- Agent/使用工具
- 服务器
- 构建服务器
- 访问服务器
基本初始化配置
Langsmith
这个主要可以观察数据请求和结果的,官网:langsmith
设置环境变量
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."
如果是windows,可以打开这个进行设置
或者使用这个(PS:我觉得不好用,每次运行都需要输入账号密码,如果是jupyter可以尝试,建议上面的方法)
import getpass
import osos.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
Language Models
通常使用chatgpt,但是国内使用chatgpt直连极易被封(即使你是gpt4会员,也会秒封,亲身经历)。建议使用API和跳转:
在用的API跳转:API
import osos.environ["OPENAI_API_KEY"] = "sk_跳转API的密钥"# openai的模型调用
from langchain_openai import ChatOpenAI#基础网址也可能是:https://one-api.bltcy.top,如果还是不行,那个API网址还有几个,尝试即可
model = ChatOpenAI(model="gpt-4",base_url='https://one-api.bltcy.top/v1')
基础指令
传递信息
.invoke
目标是将消息列表message
传递给模型,当然不一定非要是大语言模型,这个传递消息用的很多。
HumanMessage是我们要提供的信息。
SystemMessage是用于启动 AI 行为的消息,通常作为序列中的第一个传入 的输入消息。
AIMessage 从AI获取的消息,大致上可以理解为,之前的历史记录
from langchain_core.messages import HumanMessage, SystemMessagemessages = [SystemMessage(content="Translate the following from English into Italian"),HumanMessage(content="hi!"),
]print(model.invoke(messages))
# AIMessage(content='ciao!', response_metadata={'token_usage': {'completion_tokens': 3, 'prompt_tokens': 20, 'total_tokens': 23}, 'model_name': 'gpt-4', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-fc5d7c88-9615-48ab-a3c7-425232b562c5-0')
from langchain_core.messages import AIMessagemessage=[HumanMessage(content="Hi! I'm Bob"),AIMessage(content="Hello Bob! How can I assist you today?"),HumanMessage(content="What's my name?"),]
res=model.invoke(message)
print(res)
# AIMessage(content='Your name is Bob.', response_metadata={'token_usage': {'completion_tokens': 5, 'prompt_tokens': 35, 'total_tokens': 40}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-5692718a-5d29-4f84-bad1-a9819a6118f1-0')
OutputParsers 输出解析器
之前是给了一堆信息,但是,我们可能只想使用字符串响应。我们可以使用简单的输出解析器来解析此响应。
StrOutputParser()
from langchain_core.output_parsers import StrOutputParserparser = StrOutputParser()result = model.invoke(messages)print(parser.invoke(result))
#'Ciao!'
chain 链
更常见的是,我们可以用这个输出解析器“chian”模型。这意味着此输出解析器将在此链中每次被调用。该链采用语言模型的输入类型(字符串或消息列表),并返回输出解析器的输出类型(字符串)。
chain = model | parser
print(chain.invoke(messages))
#'Ciao!'
Prompt Templates 提示模板
现在,我们将消息列表直接传递到语言模型中。此消息列表从何而来?通常,它是由用户输入和应用程序逻辑的组合构建的。此应用程序逻辑通常采用原始用户输入,并将其转换为准备传递给语言模型的消息列表。常见的转换包括添加系统消息或使用用户输入设置模板的格式。
PromptTemplates 是 LangChain 中的一个概念,旨在帮助实现这种转换。它们接收原始用户输入并返回准备传递到语言模型中的数据(提示)。
简单来说,就是每次我们不想重复输入一大堆文本,我们可以事先写几个prompt告诉模型要干啥。
Prompt Templates
from langchain_core.prompts import ChatPromptTemplate
system_template = "Translate the following into {language}:"
prompt_template = ChatPromptTemplate.from_messages([("system", system_template), ("user", "{text}")]
)
result = prompt_template.invoke({"language": "italian", "text": "hi"})
print(result)
#ChatPromptValue(messages=[SystemMessage(content='Translate the following into italian:'), HumanMessage(content='hi')])
直接访问消息,可以使用to_messages()
print(result.to_messages())
[SystemMessage(content='Translate the following into italian:'),HumanMessage(content='hi')]
还可以继续形成chain,这样就方便很多了
chain = prompt_template | model | parser
res=chain.invoke({"language": "italian", "text": "hi"})
print(res)
Message History 消息历史记录
我们可以使用 Message History 类来包装我们的模型并使其有状态。这将跟踪模型的输入和输出,并将它们存储在某个数据存储中。然后,未来的交互将加载这些消息,并将它们作为输入的一部分传递到链中。
之后,我们可以导入相关类并设置我们的链,该链包装模型并添加此消息历史记录。这里的一个关键部分是我们传入的函数。 get_session_history 此函数应接受并 session_id 返回 Message History 对象。这 session_id 用于区分单独的对话,并且在调用新链时应作为配置的一部分传入。
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistorystore = {}def get_session_history(session_id: str) -> BaseChatMessageHistory:if session_id not in store:store[session_id] = ChatMessageHistory()return store[session_id]#大致上就是每次模型如果对话了,保存对话到store
with_message_history = RunnableWithMessageHistory(model, get_session_history)
我们现在需要创建一个每次都传递到可运行对象中 config 。此配置包含的信息不是直接输入的一部分,但仍然有用。在本例中,我们希望包含一个session_id
这应该如下所示:
config = {"configurable": {"session_id": "abc2"}}
#实例化执行带有记忆的model,即对话会存储内容
response = with_message_history.invoke([HumanMessage(content="Hi! I'm Bob")],config=config,
)print(response.content)
#'Hello Bob! How can I assist you today?'
response = with_message_history.invoke([HumanMessage(content="What's my name?")],config=config,
)
print(response.content)
# 'Your name is Bob.'
我们的聊天机器人现在记住了关于我们的事情。如果我们更改配置以引用不同的 session_id
,我们可以看到它重新开始对话。
config = {"configurable": {"session_id": "abc3"}}response = with_message_history.invoke([HumanMessage(content="What's my name?")],config=config,
)
print(response.content)
# "I'm sorry, I do not have the ability to know your name unless you tell me."
我们还可以利用MessagesPlaceholder
所有消息传递。
MessagesPlaceholder
请注意,这略微改变了输入类型 - 我们现在不是传入消息列表,而是传入一个带有 messages
键的字典,其中包含消息列表。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderprompt = ChatPromptTemplate.from_messages([("system","You are a helpful assistant. Answer all questions to the best of your ability.",),MessagesPlaceholder(variable_name="messages"),]
)
chain = prompt | model
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
config = {"configurable": {"session_id": "abc5"}}
response = with_message_history.invoke([HumanMessage(content="Hi! I'm Jim")],config=config,
)
print(response.content)
# 'Hello, Jim! How can I assist you today?'
response = with_message_history.invoke([HumanMessage(content="What's my name?")],config=config,
)
print(response.content)
#'Your name is Jim. How can I assist you further, Jim?'
现在让我们的提示稍微复杂一点。让我们假设提示模板现在看起来像这样:
prompt = ChatPromptTemplate.from_messages([("system","You are a helpful assistant. Answer all questions to the best of your ability in {language}.",),MessagesPlaceholder(variable_name="messages"),]
)
chain = prompt | model
with_message_history = RunnableWithMessageHistory(chain,get_session_history,input_messages_key="messages",
)
config = {"configurable": {"session_id": "abc11"}}
response = with_message_history.invoke({"messages": [HumanMessage(content="hi! I'm todd")], "language": "Spanish"},config=config,
)
print(response.content)
# '¡Hola Bob! ¿En qué puedo ayudarte hoy?'
response = with_message_history.invoke({"messages": [HumanMessage(content="whats my name?")], "language": "Spanish"},config=config,
)
print(response.content)
#'Tu nombre es Todd. ¿Hay algo más en lo que pueda ayudarte?'
Managing Conversation History 管理对话历史记录
构建聊天机器人时要了解的一个重要概念是如何管理对话历史记录。如果不进行管理,消息列表将无限制增长,并可能溢出 LLM.因此,添加一个步骤来限制您传入的消息的大小非常重要。
重要的是,您需要在提示模板之前执行此操作,但在从消息历史记录加载以前的消息之后执行此操作。
为此,我们可以在提示符前面添加一个简单的步骤来适当地修改 messages 密钥,然后将该新链包装在 Message History 类中。首先,让我们定义一个函数来修改传入的消息。让我们让它选择 k 最新的消息。然后,我们可以通过在开始时添加它来创建一个新链。
如果我们创建一个超过 10 条消息的消息列表,我们可以看到它不再记住早期消息中的信息。
from langchain_core.runnables import RunnablePassthroughdef filter_messages(messages, k=10):return messages[-k:]chain = (RunnablePassthrough.assign(messages=lambda x: filter_messages(x["messages"]))| prompt| model
)
messages = [HumanMessage(content="hi! I'm bob"),AIMessage(content="hi!"),HumanMessage(content="I like vanilla ice cream"),AIMessage(content="nice"),HumanMessage(content="whats 2 + 2"),AIMessage(content="4"),HumanMessage(content="thanks"),AIMessage(content="no problem!"),HumanMessage(content="having fun?"),AIMessage(content="yes!"),
]with_message_history = RunnableWithMessageHistory(chain,get_session_history,input_messages_key="messages",
)
config = {"configurable": {"session_id": "abc20"}}
response = with_message_history.invoke({"messages": messages + [HumanMessage(content="whats my name?")],"language": "English",},config=config,
)print(response.content)
#"I'm sorry, I don’t have access to your name. Can I help you with anything else?"
Streaming 流
LLMs有时可能需要一段时间才能做出响应,因此为了改善用户体验,大多数应用程序所做的一件事就是在生成每个令牌时流回每个令牌。
所有链都公开一个 .stream
方法,使用消息历史记录的链也不例外。我们可以简单地使用该方法来返回流式响应。
config = {"configurable": {"session_id": "abc15"}}
for r in with_message_history.stream({"messages": [HumanMessage(content="hi! I'm todd. tell me a joke")],"language": "English",},config=config,
):print(r.content, end="|")
Vector stores and retrievers 矢量存储和检索器
文档document
LangChain实现了一个文档抽象,旨在表示文本单元和关联的元数据。它有两个属性:
- page_content :表示内容的字符串;
- metadata :包含任意元数据的字典。
该metadata
属性可以捕获有关文档源、文档与其他文档的关系以及其他信息的信息。请注意,单个 Document 对象通常表示较大文档的块。
from langchain_core.documents import Documentdocuments = [Document(page_content="Dogs are great companions, known for their loyalty and friendliness.",metadata={"source": "mammal-pets-doc"},),Document(page_content="Cats are independent pets that often enjoy their own space.",metadata={"source": "mammal-pets-doc"},),Document(page_content="Goldfish are popular pets for beginners, requiring relatively simple care.",metadata={"source": "fish-pets-doc"},),Document(page_content="Parrots are intelligent birds capable of mimicking human speech.",metadata={"source": "bird-pets-doc"},),Document(page_content="Rabbits are social animals that need plenty of space to hop around.",metadata={"source": "mammal-pets-doc"},),
]
Vector stores 矢量存储
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddingsvectorstore = Chroma.from_documents(documents,embedding=OpenAIEmbeddings(base_url='https://one-api.bltcy.top/v1'),
)
#根据与嵌入查询的相似性返回文档:
embedding = OpenAIEmbeddings(base_url='https://one-api.bltcy.top/v1').embed_query("cat")
# [Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'source': 'mammal-pets-doc'}),
# Document(page_content='Dogs are great companions, known for their loyalty and friendliness.', metadata={'source': 'mammal-pets-doc'}),
# Document(page_content='Rabbits are social animals that need plenty of space to hop around.', metadata={'source': 'mammal-pets-doc'}),
# Document(page_content='Parrots are intelligent birds capable of mimicking human speech.', metadata={'source': 'bird-pets-doc'})]
Retrievers
LangChain VectorStore
对象不对 Runnable 进行子类化,因此无法立即集成到 LangChain 表达式语言链中。
LangChain检索器是可运行的,因此它们实现了一组标准方法(例如,同步和异步 invoke 以及 batch 操作),并被设计为合并到LCEL链中。
我们可以自己创建一个简单的版本,而无需子 Retriever 类化。如果我们选择我们希望使用哪种方法来检索文档,我们可以轻松创建一个可运行的。下面我们将围绕 similarity_search 该方法构建一个:
from typing import Listfrom langchain_core.documents import Document
from langchain_core.runnables import RunnableLambdaretriever = RunnableLambda(vectorstore.similarity_search).bind(k=1) # select top result
print(retriever.batch(["cat", "shark"]))
# [[Document(page_content='Cats are independent pets that often enjoy their own space.', metadata={'source': 'mammal-pets-doc'})],
# [Document(page_content='Goldfish are popular pets for beginners, requiring relatively simple care.', metadata={'source': 'fish-pets-doc'})]]
系统实例:
import getpass
import os
from langchain_core.documents import Document
from langchain_openai import ChatOpenAI
from typing import List
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
from langchain_core.runnables import RunnableLambda
import getpass
import os
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthroughos.environ['OPENAI_API_KEY'] = 'sk-'documents = [Document(page_content="Dogs are great companions, known for their loyalty and friendliness.",metadata={"source": "mammal-pets-doc"},),Document(page_content="Cats are independent pets that often enjoy their own space.",metadata={"source": "mammal-pets-doc"},),Document(page_content="Goldfish are popular pets for beginners, requiring relatively simple care.",metadata={"source": "fish-pets-doc"},),Document(page_content="Parrots are intelligent birds capable of mimicking human speech.",metadata={"source": "bird-pets-doc"},),Document(page_content="Rabbits are social animals that need plenty of space to hop around.",metadata={"source": "mammal-pets-doc"},),
]vectorstore = Chroma.from_documents(documents,embedding=OpenAIEmbeddings(base_url='https://one-api.bltcy.top/v1'),
)retriever = vectorstore.as_retriever(search_type="similarity",search_kwargs={"k": 1},
)# os.environ["OPENAI_API_KEY"] = getpass.getpass()llm = ChatOpenAI(model="gpt-3.5-turbo-0125",base_url='https://one-api.bltcy.top/v1')message = """
Answer this question using the provided context only.{question}Context:
{context}
"""prompt = ChatPromptTemplate.from_messages([("human", message)])rag_chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm
response = rag_chain.invoke("tell me about cats")print(response.content)
Agent/使用工具
我们将使用 Tavily(搜索引擎)作为工具。为了使用它,您需要获取并设置一个 API 密钥:tavily
我们首先需要创建我们想要使用的工具。我们选择的主要工具将是 Tavily - 一个搜索引擎。我们在LangChain中有一个内置工具,可以轻松地使用Tavily搜索引擎作为工具。
from langchain_community.tools.tavily_search import TavilySearchResultssearch = TavilySearchResults(max_results=2)
search_results = search.invoke("what is the weather in SF")
print(search_results)
#如果我们愿意,我们可以创建其他工具。
#一旦我们有了我们想要的所有工具,我们可以把它们放在一个列表中,我们以后会引用它们。
tools = [search]
print(tools)
使用工具可以:
model_with_tools = model.bind_tools(tools)response = model_with_tools.invoke([HumanMessage(content="What's the weather in SF?")])print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
#ContentString: [{'id': 'toolu_01TSdZjtqppPVYyvrYvsok6d', 'input': {'query': 'san francisco weather'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
#ToolCalls: [{'name': 'tavily_search_results_json', 'args': {'query': 'san francisco weather'}, 'id': 'toolu_01TSdZjtqppPVYyvrYvsok6d'}]
现在我们已经定义了工具和 LLM,我们可以创建代理了。我们将使用 LangGraph 来构建代理。目前,我们正在使用一个高级接口来构建代理,但 LangGraph 的好处是,这个高级接口由一个低级、高度可控的 API 支持,以防你想修改代理逻辑。
from langgraph.prebuilt import create_react_agentagent_executor = create_react_agent(model, tools)response = agent_executor.invoke({"messages": [HumanMessage(content="whats the weather in sf?")]}
)
response["messages"]
如前所述,此代理是无状态的。这意味着它不记得以前的交互。为了给它内存,我们需要传入一个检查点。传入检查点时,我们还必须在调用代理时传入一个 thread_id (以便它知道要从哪个线程/对话恢复)。
from langgraph.checkpoint.sqlite import SqliteSavermemory = SqliteSaver.from_conn_string(":memory:")agent_executor = create_react_agent(model, tools, checkpointer=memory)config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream({"messages": [HumanMessage(content="hi im bob!")]}, config
):print(chunk)print("----")
# {'agent': {'messages': [AIMessage(content="Hello Bob! It's nice to meet you again.", response_metadata={'id': 'msg_013C1z2ZySagEFwmU1EsysR2', 'model': 'claude-3-sonnet-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 1162, 'output_tokens': 14}}, id='run-f878acfd-d195-44e8-9166-e2796317e3f8-0', usage_metadata={'input_tokens': 1162, 'output_tokens': 14, 'total_tokens': 1176})]}}
----
for chunk in agent_executor.stream({"messages": [HumanMessage(content="whats my name?")]}, config
):print(chunk)print("----")# {'agent': {'messages': [AIMessage(content='You mentioned your name is Bob when you introduced yourself earlier. So your name is Bob.', response_metadata={'id': 'msg_01WNwnRNGwGDRw6vRdivt6i1', 'model': 'claude-3-sonnet-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 1184, 'output_tokens': 21}}, id='run-f5c0b957-8878-405a-9d4b-a7cd38efe81f-0', usage_metadata={'input_tokens': 1184, 'output_tokens': 21, 'total_tokens': 1205})]}}
----
完整示例
# Import relevant functionality
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.prebuilt import create_react_agent
import osos.environ['TAVILY_API_KEY'] = 'tvly-'
os.environ['OPENAI_API_KEY'] = 'sk-'memory = SqliteSaver.from_conn_string(":memory:")
model = ChatOpenAI(base_url='https://one-api.bltcy.top/v1')
search = TavilySearchResults(max_results=2)
tools = [search]
agent_executor = create_react_agent(model, tools, checkpointer=memory)# Use the agent
config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream({"messages": [HumanMessage(content="hi im bob! and i live in sf")]}, config
):print(chunk)print("----")for chunk in agent_executor.stream({"messages": [HumanMessage(content="whats the weather where I live?")]}, config
):print(chunk)print("----")
服务器
构建服务器
为了为我们的应用程序创建一个服务器,我们将创建一个 serve.py 文件。这将包含我们为应用程序提供服务的逻辑。它由三件事组成:
- 我们刚刚在上面构建的链的定义
- 我们的 FastAPI 应用程序
- 为链提供服务的路由的定义,这是通过 langserve.add_routes
PS:官网案例,记得修改api
#!/usr/bin/env python
from typing import Listfrom fastapi import FastAPI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langserve import add_routes# 1. Create prompt template
system_template = "Translate the following into {language}:"
prompt_template = ChatPromptTemplate.from_messages([('system', system_template),('user', '{text}')
])# 2. Create model
model = ChatOpenAI()# 3. Create parser
parser = StrOutputParser()# 4. Create chain
chain = prompt_template | model | parser# 4. App definition
app = FastAPI(title="LangChain Server",version="1.0",description="A simple API server using LangChain's Runnable interfaces",
)# 5. Adding chain routeadd_routes(app,chain,path="/chain",
)if __name__ == "__main__":import uvicornuvicorn.run(app, host="localhost", port=8000)
核心就这个部分:
# 创建服务器API设置
app = FastAPI(title="LangChain Server",version="1.0",description="A simple API server using LangChain's Runnable interfaces",
)# 第一个是服务器设置
# 第二个是模型的思考链路
# 第三个是是路由
add_routes(app,chain,path="/chain",
)if __name__ == "__main__":import uvicornuvicorn.run(app, host="localhost", port=8000)
执行
python serve.py
访问服务器
运行之后可以使用:http://localhost:8000/chain/playground/
或者可以设置客户端访问:
设置一个客户端,以便以编程方式与我们的服务进行交互。我们可以使用 [langserve.RemoteRunnable](/docs/langserve/#client)
.使用它,我们可以与服务链进行交互,就好像它在客户端运行一样。
client.py
from langserve import RemoteRunnableremote_chain = RemoteRunnable("http://localhost:8000/chain/")
remote_chain.invoke({"language": "italian", "text": "hi"})
python client.py