用Streamlit、LangChain和ChatGPT用30行代码创建AI数学辅导App

#暑期创作大赛#

用Streamlit,LangChain,和ChatGPT用30行代码创建你的AI数学辅导App:

用Streamlit、LangChain和ChatGPT用30行代码创建AI数学辅导App

大型语言模型(LLMs)的进步吸引了大量关注。虽然有人认为这只是炒作,但许多人看到了它为开发新产品或改进现有产品的机会。

新的基于LLM的产品和插件迅速推出,推动这种生产力的三个主要原因是:

1. LLMs变得超级强大

2. 我们已经学会了如何更好地使用它们(例如提示工程)

3. 有工具可以加速并简化开发过程(例如Streamlit,LangChain)

在这篇文章中,我们将创建一个30行代码的数学辅导应用。

大纲

文章包括以下部分:

  • 项目分解
  • 提示工程
  • LangChain
  • Streamlit
  • 整合
  • 改进领域和结论

项目分解

这个项目主要包含三个部分:

1.提示工程:虽然LLMs是高度有能力的工具,但是你与它们的交互方式在获取有用和准确的回应上起着关键的作用。因此,你如何编写你的提示非常重要。我们将学习提示工程的最佳实践,例如链式思维和少量学习。

2.LangChain:这是一个开源开发框架,用于创建基于LLM的应用。

3.Streamlit:这是一个开发框架,用于将您的数据脚本快速轻松地转换为Web应用。它不需要任何前端经验,你需要的只是Python,我们将详细介绍每个部分。

提示工程

随着LLMs的兴起,提示工程进入了我们的生活。在高层次上,它关注优化提示(即输入到LLM)以有效地查询LLMs并获得准确和期望的回应。

提示工程正在成为一个全新的学科,因此在创建提示时有许多事情要考虑。

对于我们的数学辅导应用中使用的提示,我们将采用两种提示技术:

  • 链式思维提示:一个具有{输入,链式思维,输出}结构的提示技术,其中链式思维是一系列中间自然语言推理步骤。我们将使用它来创建连续的步骤以达到解决方案。
  • 少量学习:它简单地意味着在提示中添加输入-输出例子,以便模型知道我们期望什么。

这是我们将在我们的应用中使用的提示:

prompt= """充当一个辅导员,帮助学生解决数学和算术推理问题。学生会向你提问。一步步思考以得到答案。写下每个推理步骤。
你会被要求显示答案,或给出能帮助学生自己得出答案的线索。以下是一些示例问题,带有预期的答案和线索:
问题:约翰有2栋房子。每栋房子有3个卧室,每个卧室有2个窗户。
每栋房子有1个带2个窗户的厨房。此外,每栋房子还有5个不在卧室或厨房的窗户。
约翰的房子里有多少个窗户?
答案:每栋房子有3个卧室,每个卧室有2个窗户,所以每栋房子有3 x 2 = 6个窗户。
每栋房子还有1个带2个窗户的厨房,所以每栋房子有2 x 1 = 2个窗户。
每栋房子还有5个不在卧室或厨房的窗户,所以每栋房子有5 x 1 = 5个窗户。
总共,每栋房子有6 + 2 + 5 = 13个窗户。
由于约翰有2栋房子,他总共有2 x 13 = 26个窗户。答案是26。
线索:1. 分别找出卧室窗户,厨房窗户,和其他窗户的数量
2. 把它们加在一起找出每栋房子的窗户总数
3. 找出所有房子的窗户总数。
问题:林地里有15棵树。林地工人今天会在林地种树。当他们完成后,会有21棵树。林地工人今天种了多少棵树?
答案:原本有15棵树。工人种树后,有21棵树。所以工人种了21 - 15 = 6棵树。答案是6。",
线索:1. 从种植后的树木总数开始,减去原来的树木数,以找出种植了多少树。
2. 用减法找出两个数字之间的差。
问题:Leah有32块巧克力,她的妹妹有42块。如果他们吃掉了35块,他们总共还剩下多少块?
答案:最初,Leah有32块巧克力。她的妹妹有42块。所以他们总共有32 + 42 = 74块。吃掉35块后,他们还剩下74 - 35 = 39块。答案是39块。
提示:1. 从他们拥有的巧克力总数开始。2. 减去他们吃掉的巧克力数量。
问题:{question}
"""

最后的问题是一个输入变量,我们会在LangChain部分讲到这一点。

当我们使用这个提示提出一个问题时,模型会同时生成答案和线索。由于它按照示例中显示的方式格式化响应,因此它将是一个标准的字符串,我们将能够从中提取答案和线索。

顺便说一句,我没有将提示包含在脚本的行数中,因为它是一个字符串,你可以根据需要进行定制(行数多或少)。

LangChain是一种开源的开发框架,用于使用大型语言模型(LLMs)的应用程序。它以组件的形式提供了抽象,以便以更有效或程序化的方式使用LLMs。

我们将使用LangChain的提示模板,并将问题作为模板的输入变量。

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
def generate_response(question):
chat = ChatOpenAI(temperature=0.0, openai_api_key=openai_api_key)
prompt_template = ChatPromptTemplate.from_template(template=prompt)
messages = prompt_template.format_messages(
question=question
)
response = chat(messages)
return response.content

ChatOpenAI是LangChain对ChatGPT API端点的抽象。在generate_response函数中,我们首先创建了一个温度值为0的ChatGPT模型。温度参数调整输出的随机性。较高的值使输出更随机,而较低的值使其更加专注和确定。

openai_api_key参数保存了API密钥,可以从OpenAI网站的API Keys菜单中获取。在创建应用程序时,我们将为用户添加一个输入框以输入他们的API密钥。

然后,我们从之前创建的提示字符串创建一个提示模板。问题被定义为模板的输入变量。下一步是创建消息,这是模型的最终输入。最后,响应变量是模型生成的输出。

Streamlit

我们的提示和模型已经准备好了。最后一步是将其转化为一个web应用程序。由于Streamlit的语法非常直观,这一步相当容易。

下面的代码行为应用程序添加了标题,并创建了一个输入框作为密码,以便用户可以输入他们的API密钥。

import streamlit as st
# add title
st.set_page_config(page_title="Your Friendly Math Tutor")
st.title("Your Friendly Math Tutor")
# add input box
openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')

API密钥被保存为openai_api_key变量,然后在generate_response函数中使用(在LangChain部分解释)。

其它用户交互在下面的代码片段中处理:

with st.form('myform'):
question = st.text_input('Enter question:', '')
clues = st.form_submit_button('Give me clues')
answer = st.form_submit_button('Show me the answer')
if not openai_api_key.startswith('sk-'):
st.warning('Please enter your OpenAI API key!', icon='⚠')
if clues and openai_api_key.startswith('sk-'):
st.info(generate_response(question).split("Clues")[1][2:])
if answer and openai_api_key.startswith('sk-'):
st.info(generate_response(question).split("Clues")[0])
  • question是一个文本输入框,用户将在其中输入由AI导师回答的问题;
  • clues是一个提交按钮,一旦点击,模型生成的线索将被显示;
  • answer是一个提交按钮。一旦点击,模型生成的答案将被显示。

条件语句检查用户是否输入了正确的API密钥以及哪个按钮被点击。

generate_response函数以以下格式返回响应作为一个字符串:

"Answer: Michael有58个高尔夫球。他在周二丢失了23个,所以他剩下58 - 23 = 35个高尔夫球。周三,他又丢失了2个,所以他在周三结束时剩下35 - 2 = 33个高尔夫球。答案是33。 线索:1. 从Michael拥有的高尔夫球的总数开始。2. 减去他在周二丢失的高尔夫球的数量。3. 减去他在周三丢失的高尔夫球的数量。"

我们使用split函数从这个响应中分别提取答案和线索。重要的是要注意,这并不是解析输出的最佳方式。更好的选项是让模型以JSON格式返回响应或使用LangChain的输出解析器。我已经将这一点作为应用程序的改进区域。

总结

让我们把这些部分组合在一起,以下是创建我们的AI数学导师应用程序的脚本:

import streamlit as st
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
st.set_page_config(page_title="Your Friendly Math Tutor")
st.title("Your Friendly Math Tutor")
openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
prompt = """
充当一个辅导员,帮助学生解决数学和算术推理问题。学生会向你提问。一步步思考以得到答案。写下每个推理步骤。
你会被要求显示答案,或给出能帮助学生自己得出答案的线索。以下是一些示例问题,带有预期的答案和线索:
问题:约翰有2栋房子。每栋房子有3个卧室,每个卧室有2个窗户。
每栋房子有1个带2个窗户的厨房。此外,每栋房子还有5个不在卧室或厨房的窗户。
约翰的房子里有多少个窗户?
答案:每栋房子有3个卧室,每个卧室有2个窗户,所以每栋房子有3 x 2 = 6个窗户。
每栋房子还有1个带2个窗户的厨房,所以每栋房子有2 x 1 = 2个窗户。
每栋房子还有5个不在卧室或厨房的窗户,所以每栋房子有5 x 1 = 5个窗户。
总共,每栋房子有6 + 2 + 5 = 13个窗户。
由于约翰有2栋房子,他总共有2 x 13 = 26个窗户。答案是26。
线索:1. 分别找出卧室窗户,厨房窗户,和其他窗户的数量
2. 把它们加在一起找出每栋房子的窗户总数
3. 找出所有房子的窗户总数。
问题:林地里有15棵树。林地工人今天会在林地种树。当他们完成后,会有21棵树。林地工人今天种了多少棵树?
答案:原本有15棵树。工人种树后,有21棵树。所以工人种了21 - 15 = 6棵树。答案是6。",
线索:1. 从种植后的树木总数开始,减去原来的树木数,以找出种植了多少树。
2. 用减法找出两个数字之间的差。
问题:Leah有32块巧克力,她的妹妹有42块。如果他们吃掉了35块,他们总共还剩下多少块?
答案:最初,Leah有32块巧克力。她的妹妹有42块。所以他们总共有32 + 42 = 74块。吃掉35块后,他们还剩下74 - 35 = 39块。答案是39块。
提示:1. 从他们拥有的巧克力总数开始。2. 减去他们吃掉的巧克力数量。
问题:{question}
"""
def generate_response(question):
chat = ChatOpenAI(temperature=0.0, openai_api_key=openai_api_key)
prompt_template = ChatPromptTemplate.from_template(template=prompt)
messages = prompt_template.format_messages(
question=question
)
response = chat(messages)
return response.content
with st.form('myform'):
question = st.text_input('Enter question:', '')
clues = st.form_submit_button('Give me clues')
answer = st.form_submit_button('Show me the answer')
if not openai_api_key.startswith('sk-'):
st.warning('Please enter your OpenAI API key!', icon='⚠')
if clues and openai_api_key.startswith('sk-'):
st.info(generate_response(question).split("Clues")[1][2:])
if answer and openai_api_key.startswith('sk-'):
st.info(generate_response(question).split("Clues")[0])

不包括提示,这是30行代码(包括空白行)。

将此脚本保存为streamlit_app.py(或者你想要的名字),然后在终端中运行streamlit run streamlit_app.py命令。

然后,应用程序将在你的默认浏览器中打开。你会看到以下内容:

然后,应用程序将在你的默认浏览器中打开。你会看到以下内容:输入你的问题,然后点击“给我线索”按钮。模型将生成能够帮助你逐步找到答案的线索。如果点击了“给我答案”按钮,那么将提供逐步解决方案。

以下是一些问题和模型的回答:

问题:我有10支铅笔。我的朋友给了我两支。然后,我丢失了四支铅笔。我还剩下多少支铅笔?

线索:1. 从你拥有的铅笔数量开始。2. 加上朋友给你的铅笔数量。3. 减去你丢失的铅笔数量。

答案:我有10支铅笔。我的朋友给了我2支,所以我有10 + 2 = 12支铅笔。然后,我丢失了4支铅笔,所以我剩下12 - 4 = 8支铅笔。答案是8。

问题:我有30美元。然后,我买了三颗糖。现在,我还剩下15美元。一颗糖果要多少钱?

线索:1. 通过从初始金额中减去剩余的钱来找出花在糖果上的总金额。2. 将总花费除以买的糖果数量,得出一颗糖果的成本。

答案:我开始时有30美元,买了三颗糖果,所以我花了3x美元在糖果上。买完糖果后,我剩下15美元,所以我有30 - 3x = 15美元。解出x,我得到x = 5。所以每颗糖果花费5美元。答案是5。

改进领域和结论 首先,这是一个AI数学导师,可以帮助你解决简单的数学和算术推理问题。它不是为大学生设计的,但我认为对二年级或三年级的孩子会有帮助。

然而,通过使用更复杂的提示和外部工具,可以扩展其能力。例如,LangChain允许在链中实现Wolfram Alpha作为一个组件。然后,我们可以让我们的应用解决高度复杂的问题。

另一个改进领域可能是逐一显示线索。我会对应用进行改进以实现这个变化。

最后,我们在本地运行了应用程序。为了使其可共享并为他人服务,我们可以将我们的数据应用放在AWS EC2(或任何其他云)实例上,这样其他人就可以访问它。一个好的替代方案可以是Streamlit Community Cloud,它让你的应用程序的托管和分享变得非常简单,我会写另一篇文章来解释如何实现这些改进。