在本文中,我们将了解如何将聊天机器人应用程序与外部 API 集成。在之前关于构建聊天机器人应用程序的文章中,我们介绍了使用 LangChain 和 OpenAI 创建具有特定功能的聊天机器人的基础知识,以及如何使用 Chainlit 为我们的聊天机器人构建 Web 应用程序。如果你没看过之前的系列,我建议你查看以前的文章,了解详细的步骤。《使用 LangChain 和 OpenAI 构建自己的聊天机器人》和 《使用 Chainlit 和 LangChain 构建聊天机器人应用程序》
本文将重点介绍如何通过将我们的聊天机器人 Scoopsie (冰淇淋助手)连接到外部 API 来增强它。您可以将 API 视为一种在程序内和程序之间提取和共享数据的可访问方式。用户可以向 API 发出请求来获取或发送数据,API 会以一些信息进行响应。我们将 Scoopsie 连接到一个 API,以从虚构的冰淇淋店获取信息,并使用这些响应来提供信息。对于大多数聊天机器人应用程序,将自定义聊天机器人链接到外部 API 可能非常有用,在某些情况下甚至是必要的。
目前我们使用 LLMChain 查询 OpenAI 的 GPT-3.5 模型来回答用户的冰淇淋相关查询:chatbot.py
python
import chainlit as cl
from langchain_openai import OpenAI
from langchain.chains import LLMChain
from prompts import ice_cream_assistant_prompt_template
from langchain.memory.buffer import ConversationBufferMemory
from dotenv import load_dotenv
load_dotenv()
@cl.on_chat_start
def query_llm():
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
conversation_memory = ConversationBufferMemory(memory_key="chat_history", max_len=50, return_messages=True)
llm_chain = LLMChain(llm=llm, prompt=ice_cream_assistant_prompt_template, memory=conversation_memory)
cl.user_session.set("llm_chain", llm_chain)
@cl.on_message
async def query_llm(message: cl.Message):
llm_chain = cl.user_session.get("llm_chain")
response = await llm_chain.acall(message.content, callbacks=[cl.AsyncLangchainCallbackHandler()])
await cl.Message(response["text"]).send()
创建虚构的商店 API
我们将首先创建一个 API 来连接到 Scoopsie。此 API 表示一个虚构的冰淇淋店,允许用户检索商店的菜单以及其他信息,例如自定义、用户评论和特别优惠。我们将利用 Flask(一个用于 Web 开发的 Python 框架)在不同的 API 端点中对上述信息进行编码。这些包括:
/menu
:用于检索风味和浇头菜单的端点。GET
/customizations
:用于检索自定义项的端点GET
/special-offers
:用于检索特别优惠的端点。GET
/user-reviews
:用于检索用户评论的端点。GET
为了让 Scoopsie 专注于提供信息,而不是处理交易或处理订单,我们将当前的范围限制在这些信息端点上。但是,您可以扩展此 API 以包含其他端点,例如允许用户提交订单的 POST 端点或其他 GET 端点。
第 1 步
让我们创建一个名为 Python 脚本,用于存储静态数据,如菜单、特别优惠、客户评论和自定义选项。以下是我们如何构建它:data_store.py
python
# Example menu, special offers, customer reviews, and customizations
menu = {
"flavors": [
{"flavorName": "Strawberry", "count": 50},
{"flavorName": "Chocolate", "count": 75}
],
"toppings": [
{"toppingName": "Hot Fudge", "count": 50},
{"toppingName": "Sprinkles", "count": 2000},
{"toppingName": "Whipped Cream", "count": 50}
]
}
special_offers = {
"offers": [
{"offerName": "Two for Tuesday", "details": "Buy one get one free on all ice cream flavors every Tuesday."},
{"offerName": "Winter Wonderland Discount", "details": "25% off on all orders above $20 during the winter season."}
]
}
customer_reviews = {
"reviews": [
{"userName": "andrew_1", "rating": 5, "comment": "Loved the chocolate flavor!"},
{"userName": "john", "rating": 4, "comment": "Great place, but always crowded."},
{"userName": "allison", "rating": 5, "comment": "Love the ice-creams and Scoopsie is super helpful!"}
]
}
customizations = {
"options": [
{"customizationName": "Sugar-Free", "details": "Available for most flavors."},
{"customizationName": "Extra Toppings", "details": "Choose as many toppings as you want for an extra $5!"}
]
}
您可以调整上述脚本以更好地满足您的特定需求。这些示例显示了每个类别的可能属性。在实际应用中,将这些数据存储在数据库中进行动态检索更为合适。
步骤 2
让我们在一个名为 ice_cream_store_app.py
的文件中设置我们的 Flask 应用程序,我们将从data_store.py
中导入数据。我们可以从导入所需的库并初始化 Flask 应用程序开始:
python
from flask import Flask, jsonify
from data_store import menu, special_offers, customer_reviews, customizations
app = Flask(__name__)
步骤 3
现在,让我们配置 API 端点函数。在 Flask 中,由于 Flask 的路由机制,这些函数直接响应 Web 请求,而不需要显式参数。
以下是端点函数:
python
@app.route('/menu', methods=['GET'])
def get_menu():
"""
Retrieves the menu data.
Returns:
A tuple containing the menu data as JSON and the HTTP status code.
"""
return jsonify(menu), 200
@app.route('/special-offers', methods=['GET'])
def get_special_offers():
"""
Retrieves the special offers data.
Returns:
A tuple containing the special offers data as JSON and the HTTP status code.
"""
return jsonify(special_offers), 200
@app.route('/customer-reviews', methods=['GET'])
def get_customer_reviews():
"""
Retrieves customer reviews data.
Returns:
A tuple containing the customer reviews data as JSON and the HTTP status code.
"""
return jsonify(customer_reviews), 200
@app.route('/customizations', methods=['GET'])
def get_customizations():
"""
Retrieves the customizations data.
Returns:
A tuple containing the customizations data as JSON and the HTTP status code.
"""
return jsonify(customizations), 200
对于上面的每个函数,用于将 Python 字典转换为 JSON 格式,然后返回成功查询的状态代码。200
步骤 4
最后,让我们将以下代码添加到我们的脚本中:ice_cream_store_app.py
python
if __name__ == '__main__':
app.run(debug=True)
可以通过在终端中运行以下命令来启动 API:
python
python ice_cream_store_app.py
应用程序运行后,您可以使用 Postman 等工具或使用 Web 浏览器查看特定端点

Ice-Cream Shop API 端点

LangChain的APIChain解释
LangChain中的链通过将复杂任务作为一系列更简单的连接操作来执行,从而简化了复杂的任务。这些链通常包含 LLM、PromptTemplates、输出解析器或外部第三方 API 等元素,我们将在本教程中重点介绍这些元素。我在关于该系列的第一篇文章中更详细地介绍了 LangChain 的 Chain 功能,您可以在此处访问。
以前,我们利用 LangChain 的 LLMChain 与 LLM 进行直接交互。现在,为了扩展 Scoopsie 与外部 API 交互的功能,我们将使用 APIChain。APIChain是一个LangChain模块,旨在将用户输入格式化为API请求。这将使我们的聊天机器人能够向外部 API 发送请求并接收来自外部 API 的响应,从而扩展其功能。
APIChain 可以配置为处理不同的 HTTP 方法(GET、POST、PUT、DELETE 等)、设置请求头并管理请求的正文。它还支持 JSON 有效负载,这些负载通常用于 RESTful API 通信。
从LangChain设置API Chain
第 1 步
我们首先将LangChain的APIChain模块以及其他必需的模块导入到我们的文件中。此脚本将托管我们所有的应用程序逻辑。您可以设置必要的环境变量,例如脚本中的 ,这些变量可由 python 库访问。chatbot.py
OPENAI_API_KEY
.env
dotenv
python
import chainlit as cl
from langchain_openai import OpenAI
from langchain.chains import LLMChain, APIChain
from langchain.memory.buffer import ConversationBufferMemory
from dotenv import load_dotenv
load_dotenv()
步骤 2
对于 APIChain 类,我们需要字符串格式的外部 API 文档来访问端点详细信息。本文档应概述 API 的端点、方法、参数和预期响应。这有助于 LLM 制定 API 请求并解析响应。将此信息定义为字典,然后将其转换为字符串以供以后使用会很有帮助。
让我们创建一个新的 python 脚本,该脚本名为虚构商店的 API,并添加文档:api_docs.py
python
import json
scoopsie_api_docs = {
"base_url": "<http://127.0.0.1:5000/>",
"endpoints": {
"/menu": {
"method": "GET",
"description": "Retrieve the menu of flavors and customizations.",
"parameters": None,
"response": {
"description": "A JSON object containing available flavors and toppings along with their counts.",
"content_type": "application/json"
}
},
"/special-offers": {
"method": "GET",
"description": "Retrieve current special offers and discounts.",
"parameters": None,
"response": {
"description": "A JSON object listing the current special offers and discounts.",
"content_type": "application/json"
}
},
"/customer-reviews": {
"method": "GET",
"description": "Retrieve customer reviews for the ice cream store.",
"parameters": None,
"response": {
"description": "A JSON object containing customer reviews, ratings, and comments.",
"content_type": "application/json"
}
},
"/customizations": {
"method": "GET",
"description": "Retrieve available ice cream customizations.",
"parameters": None,
"response": {
"description": "A JSON object listing available customizations like toppings and sugar-free options.",
"content_type": "application/json"
}
}
}
}
# Convert the dictionary to a JSON string
scoopsie_api_docs = json.dumps(scoopsie_api_docs, indent=2)
我已将自定义 API 的文档格式化为名为 的 Python 字典。此字典包括 API 的基本 URL,并在密钥下详细说明了我们的四个端点。每个端点都列出了其 HTTP 方法(全部为我们提供)、简明描述、接受的参数(这些端点没有)和预期的响应格式(包含相关数据的 JSON 对象)。然后,将字典转换为 JSON 字符串,使用 缩进 2 个空格以提高可读性。scoopsie_api_docs
endpoints
GET
json.dumps
让我们在脚本中导入此 API 文档:chatbot.py
python
from api_docs import scoopsie_api_docs
步骤 3
APIChain 需要两个提示:一个用于选择正确的 API 端点,另一个用于基于该端点创建对用户查询的简洁回复。这些提示具有默认值,但是,我们将创建自己的提示以确保个性化交互。我们可以在文件中添加以下新提示:prompts.py
python
api_url_template = """
Given the following API Documentation for Scoopsie's official
ice cream store API: {api_docs}
Your task is to construct the most efficient API URL to answer
the user's question, ensuring the
call is optimized to include only necessary information.
Question: {question}
API URL:
"""
api_url_prompt = PromptTemplate(input_variables=['api_docs', 'question'],
template=api_url_template)
api_response_template = """"
With the API Documentation for Scoopsie's official API: {api_docs}
and the specific user question: {question} in mind,
and given this API URL: {api_url} for querying, here is the
response from Scoopsie's API: {api_response}.
Please provide a summary that directly addresses the user's question,
omitting technical details like response format, and
focusing on delivering the answer with clarity and conciseness,
as if Scoopsie itself is providing this information.
Summary:
"""
api_response_prompt = PromptTemplate(input_variables=['api_docs',
'question',
'api_url',
'api_response'],
template=api_response_template)
在这里,使用提供的 API 文档 (api_url_prompt
) 为查询生成确切的 API URL。在使用 api_url_prompt
识别正确的端点后,APIChain 使用 api_response_prompt
来汇总 API 的响应来回答用户的查询。让我们在脚本中导入这些提示:api_docs
api_url_prompt
api_response_prompt
chatbot.py
python
from prompts import api_response_prompt, api_url_prompt
步骤 4
让我们设置 APIChain 以连接我们之前创建的虚构冰淇淋店的 API。LangChain 的 APIChain 模块提供了该方法,它允许我们从 LLM 和之前定义的 api 文档加载链。我们将继续将 OpenAI 的模型用于我们的 LLM。from_llm_and_api_docs()
gpt-3.5-turbo-instruct
python
# Initialize your LLM
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
api_chain = APIChain.from_llm_and_api_docs(
llm=llm,
api_docs=scoopsie_api_docs,
api_url_prompt=api_url_prompt,
api_response_prompt=api_response_prompt,
verbose=True,
limit_to_domains=["<http://127.0.0.1:5000/>"])
上面代码中的参数限制了 APIChain 可以访问的域。根据LangChain的官方文档,默认值为空元组。这意味着默认情况下不允许任何域。根据设计,这将在实例化时引发错误。如果要默认允许所有域,则可以通过。但是,出于安全原因,不建议这样做,因为它允许恶意用户向任意 URL(包括可从服务器访问的内部 API)发出请求。为了允许我们商店的 API,我们可以指定其 URL;这将确保我们的链条在受控环境中运行。limit_to_domains``None
步骤 5
在前面的教程中,我们设置了一个 LLMChain 来处理与冰淇淋相关的一般查询。我们仍然希望保留此功能,因为 Scoopsie 是一个有用的对话伙伴,同时还通过 APIChain 整合了对我们虚构商店菜单和自定义选项的访问。为了组合这些功能,我们将使用 llm_chain
用于常规查询和 api_chain
用于访问商店的 API。这需要调整我们的 Chainlit 设置,从用户会话开始就支持多个链。以下是我们如何调整装饰器:@cl.on_chat_start
python
@cl.on_chat_start
def setup_multiple_chains():
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
conversation_memory = ConversationBufferMemory(memory_key="chat_history", max_len=200, return_messages=True)
llm_chain = LLMChain(llm=llm, prompt=ice_cream_assistant_prompt, memory=conversation_memory)
cl.user_session.set("llm_chain", llm_chain)
api_chain = APIChain.from_llm_and_api_docs(llm=llm, api_docs=scoopsie_api_docs, api_url_prompt=api_url_prompt, api_response_prompt=api_response_prompt, verbose=True, limit_to_domains=["<http://127.0.0.1:5000/>"]
)
cl.user_session.set("api_chain", api_chain)
在启动新的用户会话时,此设置会实例化 llm_chain
和 api_chain
,确保 Scoopsie 能够处理各种查询。每条链都存储在用户会话中,以便于检索。
步骤 6
现在,让我们定义装饰器周围的包装函数:@cl.on_message
python
@cl.on_message
async def handle_message(message: cl.Message):
user_message = message.content.lower()
llm_chain = cl.user_session.get("llm_chain")
api_chain = cl.user_session.get("api_chain")
if any(keyword in user_message for keyword in ["menu", "customization",
"offer", "review"]):
# If any of the keywords are in the user_message, use api_chain
response = await api_chain.acall(user_message,
callbacks=[cl.AsyncLangchainCallbackHandler()])
else:
# Default to llm_chain for handling general queries
response = await llm_chain.acall(user_message,
callbacks=[cl.AsyncLangchainCallbackHandler()])
response_key = "output" if "output" in response else "text"
await cl.Message(response.get(response_key, "")).send()
在此设置中,我们检索 and 对象。如果用户消息包含反映我们虚构商店 API 端点的关键字,则应用程序将触发 APIChain。如果不是,我们假设这是一个与冰淇淋相关的一般查询,并触发 LLMChain。这是一个简单的用例,但对于更复杂的用例,您可能需要编写更复杂的逻辑来确保触发正确的链。有关 Chainlit 装饰器以及如何有效利用它们的更多详细信息。llm_chain``api_chain
步骤 7
现在我们的应用程序代码已准备就绪,我们可以启动我们的聊天机器人了。在项目目录中打开终端并运行以下命令:
python
chainlit run chatbot.py -w --port 8000
您可以通过在 Web 浏览器中导航到 http://localhost:8000 来访问聊天机器人。
演示
Scoopsie 的应用程序界面现已准备就绪!以下是展示聊天机器人运行情况的演示:

Scoopsie 聊天机器人演示:交互式冰淇淋助手在行动
结束语
我们已经成功地为一家虚构的冰淇淋店构建了一个 API,并将其与我们的聊天机器人集成。如上所述,您可以使用 Chainlit 访问聊天机器人的 Web 应用程序,其中可以访问常规查询和虚构商店的 API 端点。