大模型LLM之Langchain(二)

2、教程

2.1、基础

2.1.1、使用 LCEL 构建简单的LLM应用程序

工具:jupyter notebook 、anaconda3

运行Anaconda Prompt

查看当前虚拟环境

conda env list

创建虚拟环境

conda create -n lc python=3.8

激活环境

conda activate lc

安装内核工具

pip install ipykernel

将虚拟环境内核添加到jupyter notebook

python -m ipykernel install --user --name lc --display-name lc

查看内核

jupyter kernelspec list

删除虚拟内核

jupyter kernelspec remove 内核名

安装Langchain

pip install langchain

使用openAI

pip install -qU langchain-openai
配置信息

先编写配置信息,并设置模型为gpt-4

from langchain_openai import ChatOpenAI
import os
os.environ["OPENAI_API_KEY"]  ="sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["OPENAI_API_BASE"] = 'https://XXXXXXX'
model = ChatOpenAI(model="gpt-4")

我们首先直接使用模型。 ChatModel 是 LangChain “Runnables” 的实例,这意味着它们公开了一个用于与它们交互的标准接口。要简单地调用模型,我们可以将消息列表传递给 .invoke 该方法。

简单示例
from langchain_core.messages import HumanMessage, SystemMessage

messages = [
    SystemMessage(content="Translate the following from English into Chinese"),
    HumanMessage(content="hi!"),
]

model.invoke(messages)

得到输出

AIMessage(content='你好!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 2, 'prompt_tokens': 20, 'total_tokens': 22}, 'model_name': 'gpt-4', 'system_fingerprint': 'fp_bbdc8689fd', 'finish_reason': 'stop', 'logprobs': None}, id='run-7de43882-2964-48a4-8345-e7b05c1dfcf6-0', usage_metadata={'input_tokens': 20, 'output_tokens': 2, 'total_tokens': 22})
输出解析器

如果不需要元数据,只需要输出”你好“的结果,可以使用简单的输出解析器

from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()

方法一:保存语言模型调用的结果,然后将其传递给解析器。(不推荐)

result = model.invoke(messages)
parser.invoke(result)

方法二:我们可以使用 | Operator 轻松创建链。在 | LangChain 中,该运算符用于将两个元素组合在一起。(推荐)

chain = model | parser
chain.invoke(messages)

消息是由用户输入和应用程序逻辑的组合构建的。此应用程序逻辑通常采用原始用户输入,并将其转换为准备传递给语言模型的消息列表。常见转换包括添加系统消息或使用用户输入设置模板格式。

提示词模板

PromptTemplates 是 LangChain 中的一个概念,旨在协助进行这种转换。它们接收原始用户输入并返回准备传递到语言模型的数据。

如创建一个翻译的 PromptTemplate,它将接受两个用户变量:

  • language :要将文本翻译成的语言
  • text :要翻译的文本

首先,让我们创建一个字符串,我们将将其格式化为系统消息:

system_template = "Translate the following into {language}:"

接下来,我们可以创建 PromptTemplate。这将是 以及 system_template 一个更简单的模板的组合,用于放置要翻译的文本

prompt_template = ChatPromptTemplate.from_messages(
    [("system", system_template), ("user", "{text}")]
)

此提示模板的输入是字典。我们可以单独使用这个提示模板,看看它自己做了什么

result = prompt_template.invoke({
   "language": "italian", "text": "hi"})

result

输出结果

ChatPromptValue(messages=[SystemMessage(content='Translate the following into italian:'), HumanMessage(content='hi')])

我们可以看到它返回由两条消息组成的 a ChatPromptValue 。如果我们想直接访问消息,我们会这样做:

result.to_messages()

输出结果

[SystemMessage(content='Translate the following into italian:'),
 HumanMessage(content='hi')]
组成chain链

使用 LCEL 将组件链接在一起,现在,我们可以使用竖线 () | 运算符将其与上面的模型和输出解析器相结合:

chain = prompt_template | model | parser
chain.invoke({
   "language": "English", "text": "中国人民万岁"})

输出结果

'"Long live the Chinese people."'

如果我们看一下 LangSmith 跟踪,我们可以看到所有三个组件都显示在 LangSmith 跟踪中。

LangServe 帮助开发人员将 LangChain 链部署为 REST API。使用 LangChain 不需要使用 LangServe,但在本指南中,我们将展示如何使用 LangServe 部署应用程序。

官方代码如下:(需要单独设置api的key和url)

如果仅是提供接口服务,个人认为使用SpringAI更加靠谱,毕竟Java的服务生态体量在那里。

#!/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)
2.1.2、构建聊天机器人

我们将通过一个示例来说明如何设计和实现 LLM-powered 聊天机器人。这个聊天机器人将能够进行对话并记住以前的互动。

我们首先直接使用模型。 ChatModel 是 LangChain “Runnables” 的实例,这意味着它们公开了一个用于与它们交互的标准接口。要简单地调用模型,我们可以将消息列表传递给 .invoke 该方法。

from langchain_openai import ChatOpenAI
import os
os.environ["OPENAI_API_KEY"]  ="sk-XXXXXXXXXXXXXXX"
os.environ["OPENAI_API_BASE"] = 'https://XXXXX'
model = ChatOpenAI(model="gpt-3.5-turbo")
from langchain_core.messages import HumanMessage
model.invoke([HumanMessage(content="Hi! I'm Bob")])

输出结果

AIMessage(content='Hey Bob! What brings you here today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 12, 'total_tokens': 21}, 'model_name': 'gpt-3.5-turbo-0613', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-1bf2a317-ad67-4da7-bac6-a064a93a726d-0', usage_metadata={'input_tokens': 12, 'output_tokens': 9, 'total_tokens': 21})

再输入

model.invoke([HumanMessage(content="What's my name?")])

输出结果

AIMessage(content='I don’t know yet! Could you share your name with me?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 12, 'total_tokens': 26}, 'model_name': 'gpt-3.5-turbo-0613', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-d9d72212-df58-4c0d-b658-48e4045299b6-0', usage_metadata={'input_tokens': 12, 'output_tokens': 14, 'total_tokens': 26})

由此可见,该模型本身没有任何状态的概念。不会从历史信息中提取有效数据。

消息历史记录
方法一:

要将整个对话历史传递到模型中。

from langchain_core.messages import AIMessage

model.invoke(
    [
        HumanMessage(content="Hi! I'm Bob"),
        AIMessage(content="Hello Bob! How can I assist you today?"),
        HumanMessage(content="What's my name?"),
    ]
)

输出结果

AIMessage(content='Your name is Bob. Are you looking to delve into something specific?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 35, 'total_tokens': 49}, 'model_name': 'gpt-3.5-turbo-0613', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-e6a6a4b7-5561-4100-a242-43f230271911-0', usage_metadata={'input_tokens': 35, 'output_tokens': 14, 'total_tokens': 49})
方法二:

为了更好实现历史状态存储,可以使用 Message History 类来包装我们的模型并使其有状态。这将跟踪模型的输入和输出,并将它们存储在某个数据存储中。然后,未来的交互将加载这些消息,并将它们作为 input 的一部分传递到链中。

先安装langchain_community包

pip install langchain_community

之后,我们可以导入相关的类并设置我们的链,该链包装模型并添加此消息历史记录。这里的一个关键部分是我们作为 get_session_history .此函数应接收 session_id 并返回 Message History 对象。这 session_id 用于区分单独的对话,并且在调用新链时应作为配置的一部分传入。

from langchain_core.chat_history import (
    BaseChatMessageHistory,
    InMemoryChatMessageHistory,
)
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] = InMemoryChatMessageHistory()
    return store[session_id]


with_message_history = RunnableWithMessageHistory(model, get_session_history)

创建一个 config 每次都传递到 runnable 的 SET。此配置包含的信息不直接属于 input,但仍然有用。创建一个session_id

config = {
   "configurable": {
   "session_id": "test"}}

输入

response = with_message_history.invoke(
    [HumanMessage(content="Hi! I'm Bob")],
    config=config,
)

response.content

输出结果

'Hi, Bob! How’s it going?'

输入

response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

输出结果

"Your name is Bob. Is there something specific you'd like to talk about today?"

可见,大模型记住了历史的对话信息。

提示词模板

提示模板有助于将原始用户信息转换为LLM可以使用的格式。

首先,让我们添加一条系统消息。为此,我们将创建一个 ChatPromptTemplate。我们将利用它来 MessagesPlaceholder 传递所有消息。

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

import os
os.environ["OPENAI_API_KEY"]  ="sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["OPENAI_API_BASE"] = 'https://XXXXXXXXXXXXX'
model = ChatOpenAI(model="gpt-3.5-turbo")
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
from langchain_core.chat_history import (
    BaseChatMessageHistory,
    InMemoryChatMessageHistory,
)
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] = InMemoryChatMessageHistory()
    return store[session_id]



from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import (
    BaseChatMessageHistory,
    InMemoryChatMessageHistory,
)
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 bob")], "language": "Chinese"},
    config=config,
)

response.content
'你好,Bob!你想聊些什么呢?'
response = with_message_history.invoke(
    {
   "messages": [HumanMessage(content="whats my name?")], "language": "Chinese"},
    config=config,
)

response.content
'你的名字是Bob。如果有其他问题或想讨论的话题,随时告诉我!'
管理对话历史记录

构建聊天机器人时要了解的一个重要概念是如何管理对话历史记录。如果不进行管理,消息列表将变得不受限制。

需要在提示模板之前执行此操作,但在从 Message History 加载以前的消息之后执行此操作。

为此,我们可以在 prompt 前面添加一个简单的步骤,以适当地修改 messages 密钥,然后将该新链包装在 Message History 类中。

LangChain 附带了一些内置的 helpers,用于管理消息列表。在本例中,我们将使用 trim_messages 帮助程序来减少我们发送到模型的消息数量。修剪器允许我们指定要保留的令牌数量,以及其他参数,例如我们是否要始终保留系统消息以及是否允许部分消息:

from langchain_core.messages import SystemMessage, trim_messages
from langchain_core.messages import AIMessage
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
import os
os.environ["OPENAI_API_KEY"]  ="sk-XXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["OPENAI_API_BASE"] = 'https://XXXXXXXXXXXXXXXXX'
model = ChatOpenAI(model="gpt-3.5-turbo")

trimmer = trim_messages(
    max_tokens=65,
    strategy="last",
    token_counter=model,
    include_system=True,
    allow_partial=False,
    start_on="human",
)

messages = [
    SystemMessage(content="you're a good assistant"),
    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!"),
]

trimmer.invoke(messages)

输出结果

[SystemMessage(content="you're a good assistant"),
 HumanMessage(content='whats 2 + 2'),
 AIMessage(content='4'),
 HumanMessage(content='thanks'),
 AIMessage(content='no problem!'),
 HumanMessage(content='having fun?'),
 AIMessage(content='yes!')]

对被裁剪的部分进行提问,大模型就无法回答

from operator import itemgetter

from langchain_core.runnables import RunnablePassthrough

chain = (
    RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)
    | prompt
    | model
)

response = chain.invoke(
    {
   
        "messages": messages + [HumanMessage(content="what's my name?")],
        "language": "English",
    }
)
response.content

输出结果

'I don’t know your name yet. Would you like to share it with me?'

对未被裁剪的部分可以正确回答

response = chain.invoke(
    {
   
        "messages": messages + [HumanMessage(content="what math problem did i ask")],
        "language": "English",
    }
)
response.content

输出结果

'You asked for the result of 2 + 2.'

如果将其包装在 Message History 中,对话历史记录中可以访问的更多信息不再可用!

with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="messages",
)

config = {
   "configurable": {
   "session_id": "abc20"}}
response = with_message_history.invoke(
    {
   
        "messages": [HumanMessage(content="what math problem did i ask?")],
        "language": "English",
    },
    config=config,
)

response.content

输出结果

"You haven't asked a math problem yet. What do you need help with?"
流式显示

LLMs有时可能需要一段时间才能响应,因此,为了改善用户体验,大多数应用程序所做的一件事是在生成每个令牌时将其流式传输回去。这样,用户就可以看到进度。

所有链都公开一个 .stream 方法,使用消息历史记录的链也不例外。我们可以简单地使用该方法来获取流式响应。

config = {
   "configurable": {
   "session_id": "abc1115"}}
for r in with_message_history.stream(
    {
   
        "messages": [HumanMessage(content="讲一个至少500字的奇幻故事")],
        "language": "Chinese",
    },
    config=config,
):
    print(r.content, end="|")

输出结果

|在|一个|遥|远|的|王|国|,|那里|有|一|片|神|秘|的|森林|,|名|为|梦|幻|森林|。|传|说|这|片|森林|里|居|住|着|各种|奇|异|的|生|物|,有|会|说|话|的|动物|、|会|施|法|的|植物|,还有|神|秘|的|精|灵|。|每|当|月|圆|之|夜|,|森林|中的|一|切|都会|被|月|光|的|魔|力|唤|醒|,|流|光|溢|彩|,|宛|如|梦|境|。

|在|这个|王|国|的|边|缘|,有|一个|名|叫|小|羽|的|女孩|。|小|羽|从|小|就|对|梦|幻|森林|充|满|了|好|奇|,她|常|常|听|村|子|里的|老人|讲|述|那些|关于|森林|的|奇|异|故事|。|她|梦想|着|有|一天|能够|进入|梦|幻|森林|,|去|探索|那|里的|奥|秘|。

|一天|夜|里|,小|羽|在|窗|前|望|着|明|亮|的|月|亮|,|心|中|涌|起|一|股|冲|动|。|她|决定|趁|着|月|光|的|照|耀|,|偷偷|溜|出|家|门|,|前|往|梦|幻|森林|。|她|带|上|了|一个|小|背|包|,|里面|装|着|一些|食|物|和|水|,|便|踏|上|了|冒|险|的|旅|程|。

|小|羽|走|进|森林|,|月|光|透|过|树|梢|洒|在|地|面|上|,|照|亮|了|她|前|行|的|道路|。|随着|她|深入|森林|,|四|周|的|景|象|愈|发|奇|幻|,|五|彩|斑|斓|的|花|朵|争|相|开放|,|树|木|似|乎|在|低|声|细|语|。|小|羽|感|到|无|比|兴|奋|,|然而|,|内|心|深|处|也|掠|过|一|丝|不|安|。

|就在|这|时|,小|羽|听|到了|一个|轻|柔|的|声音|:“|欢迎|你|,小|女孩|。”|她|回|头|一|看|,|发现|一|只|美|丽|的|蓝|色|小|鸟|正|停|在|一|根|树|枝|上|,|目|不|转|睛|地|看|着|她|。|小|羽|惊|讶|地|问|:“|你|会|说|话|?”

|“|当然|,”|小|鸟|微|笑|着|回答|,“|我|叫|蓝|翼|,是|梦|幻|森林|的|守|护|者|之一|。|你|为何|来到|这里|?”

|小|羽|激|动|地|说|:“|我|一直|想|来|这里|探|险|,|听|说|这里|有|许|多|奇|妙|的|事情|。”

|蓝|翼|点|了|点|头|,|表示|理解|:“|但|你|必须|小|心|,|梦|幻|森林|并|不是|所有|地方|都|安全|,有|些|地方|隐藏|着|危险|。|跟|我|来|,我|可以|带|你|去|一个|特别|的|地方|。”

|小|羽|跟|随|蓝|翼|,|穿|过|了|几|条|小|路|,|来到|了一|片|开|阔|地|。|那里|有|一|片|闪|闪|发|光|的|湖|泊|,|湖|水|如|同|星|空|般|璀|璨|。|蓝|翼|告诉|小|羽|,这|个|湖|泊|叫|做|星|湖|,|传|说|每|当|月|圆|之|夜|,|湖|水|会|显|现|出|人|们|最|深|处|的|愿|望|。

|小|羽|在|星|湖|边|静|静|坐|下|,|闭|上|眼|睛|,|心|中|默|默|许|下|愿|望|:“|我|希望|能够|永|远|探索|这个|奇|幻|的|世界|。”|就在|她|许|下|愿|望|的|瞬|间|,|湖|面|荡|漾|起|一|阵|涟|漪|,|星|光|闪|烁|,|仿|佛|回应|着|她|的|心|愿|。

|突然|,|湖|水|中|冒|出|一道|光|芒|,一个|身|穿|银|色|衣|袍|的|精|灵|缓|缓|升|起|。|精|灵|微|笑|着|看|着|小|羽|:“|你|心|中|纯|净|的|愿|望|感|动|了|星|湖|,我|愿|意|赋|予|你|探索|梦|幻|森林|的|力量|。但|要|记|住|,|力量|并|不|意味着|可以|无|所|顾|忌|,你|必须|尊|重|这个|世界|的|每|一个|生命|。”

|小|羽|欣|然|接受|了|这个|力量|,|感|激|地|向|精|灵|道|谢|。|她|意识|到|,|自己|不仅|仅|是|一个|探|险|者|,更|是|这个|森林|的|守|护|者|。|随着|时间|的|推|移|,小|羽|在|梦|幻|森林|中|经历|了|无|数|的|冒|险|,|结|识|了|各种|朋友|,|学|会|了|如何|与|自然|和|谐|共|处|。

|从|此|以后|,小|羽|不|再|是|一个|普通|的|女孩|,而|是|梦|幻|森林|的|守|护|者|之一|,|守|护|着|这个|奇|妙|的|世界|,|继续|着|她|的|探索|之|旅|。|每|当|月|圆|之|夜|,她|都会|在|星|湖|边|静|静|许|愿|,|感谢|那|份|来自|森林|的|神|秘|力量|。||
2.1.3、向量存储检索器

主要概念包含:Documents、Vector stores、Retrievers

程需要 langchainlangchain-chromalangchain-openai

pip install langchain langchain-chroma langchain-openai
Documents文件

LangChain 实现了一个 Document 抽象,它旨在表示一个文本单元和关联的元数据。它有两个属性:

  • page_content :表示内容的字符串;
  • metadata :包含任意元数据的 dict。

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

在这里,我们生成了 5 个文档,其中包含指示 3 个不同 “源” 的元数据。

Vector stores矢量存储

矢量搜索是存储和搜索非结构化数据(如非结构化文本)的常用方法。这个想法是存储与文本关联的数字向量。给定一个查询,我们可以将其嵌入为相同维度的向量,并使用向量相似度指标来识别商店中的相关数据。

LangChain VectorStore 对象包含用于将文本和 Document 对象添加到存储区以及使用各种相似性指标查询它们的方法。它们通常使用嵌入模型进行初始化,嵌入模型确定如何将文本数据转换为数字向量。

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

vectorstore = Chroma.from_documents(
    documents,
    embedding=OpenAIEmbeddings(),
)

在此处调用 .from_documents 会将文档添加到向量存储中。VectorStore 实现了用于添加文档的方法,这些方法也可以在对象实例化后调用。大多数实现都允许您连接到现有的 vector store —— 例如,通过提供 client、索引名称或其他信息。

一旦我们实例化了包含文档的 a VectorStore ,我们就可以查询它。VectorStore 包括用于查询的方法:

  • 同步和异步
  • 按字符串查询和按向量
  • 返回和不返回相似性分数
  • 通过相似性和最大边际相关性(以平衡相似性与查询与检索结果中的多样性)

这些方法的输出中通常包括 Document 对象的列表。

查询示例
根据与字符串查询的相似性返回文档:
vectorstore.similarity_search("Goldfish")

输出结果

[Document(metadata={'source': 'fish-pets-doc'}, page_content='Goldfish are popular pets for beginners, requiring relatively simple care.'),
 Document(metadata={'source': 'fish-pets-doc'}, page_content='Goldfish are popular pets for beginners, requiring relatively simple care.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.')]
异步查询
await vectorstore.asimilarity_search("dog")

输出结果

[Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Cats are independent pets that often enjoy their own space.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Cats are independent pets that often enjoy their own space.')]
返回分数

注意提供者实现不同的分数;这里的Chroma 返回一个距离度量,该度量应该与相似性成反比变化。

vectorstore.similarity_search_with_score("cat")

输出结果

[(Document(metadata={'source': 'mammal-pets-doc'}, page_content='Cats are independent pets that often enjoy their own space.'),
  0.375326931476593),
 (Document(metadata={'source': 'mammal-pets-doc'}, page_content='Cats are independent pets that often enjoy their own space.'),
  0.375326931476593),
 (Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.'),
  0.4833090305328369),
 (Document(metadata={'source': 'mammal-pets-doc'}, page_content='Dogs are great companions, known for their loyalty and friendliness.'),
  0.4833090305328369)]
根据与嵌入式查询的相似性返回文档:
embedding = OpenAIEmbeddings().embed_query("Rabbits")
vectorstore.similarity_search_by_vector(embedding)

输出结果

[Document(metadata={'source': 'mammal-pets-doc'}, page_content='Rabbits are social animals that need plenty of space to hop around.'),
 Document(metadata={'source': 'mammal-pets-doc'}, page_content='Rabbits are social animals that need plenty of space to hop around.'),
 Document(metadata={'source': 'fish-pets-doc'}, page_content='Goldfish are popular pets for beginners, requiring relatively simple care.'),
 Document(metadata={'source': 'fish-pets-doc'}, page_content='Goldfish are popular pets for beginners, requiring relatively simple care.')]
Retrievers检索器

LangChain VectorStore 对象不是 Runnable 的子类,因此不能立即集成到 LangChain 表达式语言链中

LangChain Retriever 是 Runnables,因此它们实现了一组标准的方法(例如,同步和异步 invoke 以及 batch 操作),并被设计为合并到 LCEL 链中。

我们可以自己创建一个 this 的简单版本,而无需子类化 Retriever 。如果我们选择希望使用什么方法来检索文档,我们可以轻松地创建一个 runnable。下面我们将围绕该方法 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

retriever.batch(["cat", "shark"])

输出结果

[[Document(metadata={'source': 'mammal-pets-doc'}, page_content='Cats are independent pets that often enjoy their own space.')],
 [Document(metadata={'source': 'fish-pets-doc'}, page_content='Goldfish are popular pets for beginners, requiring relatively simple care.')]]

Vectorstores 实现了一个 as_retriever 将生成 Retriever 的方法,特别是 VectorStoreRetriever。这些检索器包括 specific search_typesearch_kwargs attributes,用于标识要调用的基础向量存储的方法以及如何参数化它们。例如,我们可以用下面的代码来复制上面的内容:

retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={
   "k": 1},
)

retriever.batch(["cat", "shark"])

会得到与上面相同的结果,这样就将vectorstore转换成了Retrievers,进而可以在langchain语言表达式中进行使用检索了

检索器可以很容易地合并到更复杂的应用程序中,例如检索增强生成 (RAG) 应用程序,这些应用程序将给定问题与检索到的上下文合并到提示中。

比如只让OpenAI根据我提供的内容回答关于猫的问题,不让大模型自己发挥

下面可以进行对比实验:

情况一:

不适用检索器,让大模型自己发挥

from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4")
from langchain_core.messages import HumanMessage, SystemMessage

messages = [
    SystemMessage(content="请回答相关动物的提问"),
    HumanMessage(content="猫"),
]

model.invoke(messages)

输出结果

AIMessage(content='猫是非常有趣的动物,具有独特的性格和行为。你对猫有什么特别的问题或是想了解什么吗?比如它们的行为、品种、养护方法等?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 19, 'total_tokens': 63}, 'model_name': 'gpt-4', 'system_fingerprint': 'fp_3aa7262c27', 'finish_reason': 'stop', 'logprobs': None}, id='run-5173bdb9-c3ce-4cd6-a78d-886432475a91-0', usage_metadata={'input_tokens': 19, 'output_tokens': 44, 'total_tokens': 63})
情况二:

使用检索器,根据指定的内容进行回答

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
llm = ChatOpenAI(model="gpt-4")


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)

输出结果

Cats are independent pets that often enjoy their own space.

关联到了上文documents中的内容

2.1.4、构建代理

语言模型本身无法执行操作 - 它们只是输出文本。LangChain 的一大用例是创建代理。代理是用作LLMs推理引擎的系统,用于确定要采取哪些操作以及要传递这些操作的输入。执行操作后,可以将结果反馈到 中LLM,以确定是否需要更多操作,或者是否可以完成。

在本教程中,我们将构建一个可以与搜索引擎交互的代理。您将能够向该代理提问,观看它调用搜索工具,并与它进行对话。简单点就是让AI干其他的活,不仅仅是回答一些简单的问题,下面以查询天气为例。

如果直接让OpenAI查询天气,它是无法完成的

import os
from langchain_openai import ChatOpenAI
os.environ["OPENAI_API_KEY"]  ="sk-J2zPtsP56osgxukGC78dF1B13dD74d178aB1478761506fCe"
os.environ["OPENAI_API_BASE"] = 'https://api.xty.app/v1'
model = ChatOpenAI(model="gpt-4")
from langchain_core.messages import HumanMessage, SystemMessage

messages = [
    HumanMessage(content="说一下北京今天的天气,包括气温、湿度等尽量详细"),
]

model.invoke(messages)

输出结果

AIMessage(content='抱歉,我无法提供当天的天气信息。要获取最新的天气情况,包括气温、湿度等详细信息,建议使用天气应用程序或访问气象网站,如中国气象局、中央气象台或其他提供实时天气更新的服务。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 55, 'prompt_tokens': 23, 'total_tokens': 78}, 'model_name': 'gpt-4', 'system_fingerprint': 'fp_cbac5eb3c0', 'finish_reason': 'stop', 'logprobs': None}, id='run-f6560897-51d2-42cf-8a08-5896e334a4aa-0', usage_metadata={'input_tokens': 23, 'output_tokens': 55, 'total_tokens': 78})

可见AI无法查询实时天气,需要基于Ai搭建一个代理(智能体),让他自己去查天气,用户只需要提问就行

查询天气的AI使用的是 Tavily ,Tavily搜索API是一款针对LLM和RAG优化的搜索引擎,旨在提供高效、快速且持久的搜索结果。

官网:https://tavily.com/,他的API免费使用1000次/月

先安装包

pip install -U langchain-community langgraph langchain-anthropic tavily-python langgraph-checkpoint-sqlite

整体逻辑如下
在这里插入图片描述
用户提出问题,OpenAI根据提出问题使用搜索引擎查询,将查询的结果整理后给到用户。

# Import relevant functionality
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
import os
# Create the agent
memory = MemorySaver()
model = ChatAnthropic(model_name="claude-3-sonnet-20240229")
os.environ["TAVILY_API_KEY"] = "tvly-XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["OPENAI_API_KEY"]  ="sk-XXXXXXXXXXXXXXXXXXXXXXXXXXX"
os.environ["OPENAI_API_BASE"] = 'https://XXXXXXXX'

from langchain_community.tools.tavily_search import TavilySearchResults

search = TavilySearchResults(max_results=2)
search_results = search.invoke("what is the weather in Beijing")
print(search_results)
# If we want, we can create other tools.
# Once we have all the tools we want, we can put them in a list that we will reference later.
tools = [search]

输出结果

[{'url': 'https://www.weatherapi.com/', 'content': "{'location': {'name': 'Beijing', 'region': 'Beijing', 'country': 'China', 'lat': 39.93, 'lon': 116.39, 'tz_id': 'Asia/Shanghai', 'localtime_epoch': 1725177978, 'localtime': '2024-09-01 16:06'}, 'current': {'last_updated_epoch': 1725177600, 'last_updated': '2024-09-01 16:00', 'temp_c': 31.3, 'temp_f': 88.3, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 13.6, 'wind_kph': 22.0, 'wind_degree': 330, 'wind_dir': 'NNW', 'pressure_mb': 1009.0, 'pressure_in': 29.8, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 36, 'cloud': 0, 'feelslike_c': 32.6, 'feelslike_f': 90.7, 'windchill_c': 29.9, 'windchill_f': 85.9, 'heatindex_c': 30.6, 'heatindex_f': 87.1, 'dewpoint_c': 16.6, 'dewpoint_f': 61.8, 'vis_km': 10.0, 'vis_miles': 6.0, 'uv': 7.0, 'gust_mph': 14.4, 'gust_kph': 23.1}}"}, {'url': 'https://www.easeweather.com/asia/china/beijing/september', 'content': 'The forecast for the first days of September 2024 in Beijing predicts a temperature of 27.8 ° C, which is slightly above the historical average.. In general, the average temperature in Beijing at the beginning of September is 26.9 ° C.As the month progressed, temperatures tended to moderately fall, reaching an average of 25.4 ° C by the end of September.'}]

创建要使用的AI模型,本次还是选择OpenAI,并将AI与tool进行绑定,这样OpenAI具有使用工具(搜索引擎)的能力

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4")

model_with_tools = model.bind_tools(tools)
response = model_with_tools.invoke([HumanMessage(content="What's the weather in Beijing!")])

print(f"ContentString: {
     response.content}")
print(f"ToolCalls: 
  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空指针异常Null_Point_Ex

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值