学习笔记(一)——Langchain基本操作与函数

学习笔记(一)——Langchain基本操作与函数

基本初始化配置

Langsmith

这个主要可以观察数据请求和结果的,官网:langsmith

设置环境变量

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."

如果是windows,可以打开这个进行设置
在这里插入图片描述

或者使用这个(PS:我觉得不好用,每次运行都需要输入账号密码,如果是jupyter可以尝试,建议上面的方法)

import getpass
import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()

Language Models

通常使用chatgpt,但是国内使用chatgpt直连极易被封(即使你是gpt4会员,也会秒封,亲身经历)。建议使用API和跳转:

在用的API跳转:API

import os

os.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, SystemMessage

messages = [
    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 AIMessage


message=[
        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 StrOutputParser

parser = 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 RunnableWithMessageHistory

store = {}

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, MessagesPlaceholder

prompt = 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 RunnablePassthrough

def 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 Document

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"},
    ),
]

Vector stores 矢量存储

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

vectorstore = 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 List

from langchain_core.documents import Document
from langchain_core.runnables import RunnableLambda

retriever = 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 RunnablePassthrough

os.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 TavilySearchResults

search = 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_agent

agent_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 SqliteSaver

memory = 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 os

os.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 文件。这将包含我们为应用程序提供服务的逻辑。它由三件事组成:

  1. 我们刚刚在上面构建的链的定义
  2. 我们的 FastAPI 应用程序
  3. 为链提供服务的路由的定义,这是通过 langserve.add_routes

PS:官网案例,记得修改api

#!/usr/bin/env python
from typing import List

from 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 route

add_routes(
    app,
    chain,
    path="/chain",
)

if __name__ == "__main__":
    import uvicorn

    uvicorn.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 uvicorn

    uvicorn.run(app, host="localhost", port=8000)

执行

python serve.py

访问服务器

运行之后可以使用:http://localhost:8000/chain/playground/

或者可以设置客户端访问:

设置一个客户端,以便以编程方式与我们的服务进行交互。我们可以使用 [langserve.RemoteRunnable](/docs/langserve/#client).使用它,我们可以与服务链进行交互,就好像它在客户端运行一样。

client.py

from langserve import RemoteRunnable

remote_chain = RemoteRunnable("http://localhost:8000/chain/")
remote_chain.invoke({"language": "italian", "text": "hi"})
python client.py
  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值