LangChain的开发流程
文章目录
- LangChain的开发流程
- 开发密钥指南
- 3种使用密钥的方法
- 编写一个取名程序
- LangChain表达式
LangChain的开发流程
为了更深人地理解LangChain的开发流程,本文将以构建聊天机器人为实际案例进行详细演示。下图展示了一个设计聊天机器人的LLM应用程序。
除了Wb服务器等传统组件,这个应用程序架构中还引人了两个额外的组件:一个LLM集成中间件,如LangChain(图上的中间部分),以及一个大语言模型(上图左侧)。中间件提供一个API,业务逻辑控制器调用它以启用聊天机器人功能。具体的LLM是基于配置决定的。当用户提问时(步骤①),聊天机器人控制器代码调用LangChain API(通过LangChain的6大模块设置的接口),在内部与LLM(步骤②)交互,由LLM来理解问题并生成回答(步骤③),显示在终端用户的聊天界面上(上图右侧的Web页面)。
清单1展示了如何使用LangChain和OpenAI的GPT-3.5-Turbo-0613大语言模型实现聊天机器人业务逻辑。这段Python代码首先创建了ChatOpenAI类的实例(代表GPT-3.5聊天模型包装器)。第49行在路径chat’下建立了一个POST端点,可以利用FastAPI库。当用户向聊天机器人提交一个问题时,chat函数就会被触发,请求对象在其输人属性中封装用户的提问。为了处理请求,代码第7行实例化了一个LLMChain链组件,接收了一个聊天模型包装器Ilm和一个提示词模板prompt,实现了一个LangChain的内置预配置聊天机器人,可以与终端用户交互。第8行处理用户的提问:运行LLMChain链组件,接收用户的提问并将其作为输人,返回大语言模型生成的响应。这个响应持有对用户提问的答案,并在第9行代码执行后返回给用户。
llm = ChatOpenAI(#LLM initialization parameters
model_name=("gpt-3.5-turbo-0613",openai_api_key="你的密钥"',
temperature=0.9)
_prompt=""你是一个发言友好的AI助理。请现在回答用户的提问:《question}。""
@app.post ("/chat")#Chatbot controller URL endpoint
async def chat (request):
prompt PromptTemplate.from template(prompt)
chat_chain = LLMChain(llm=llm,prompt=prompt)
response=chat_chain(request.input)#终端用户的提问字符串
return {"response":response ["text"])
开发密钥指南
LangChain自身是一个集成框架,不需要开发者注册和登录,也不需要设置密钥。但是在LLM开发过程中,要使用第三方平台的模型或者工具,需要遵守第三方的开发者协议,而且几乎所有的付费平台都使用密钥作为API调用的计费依据,这一点不仅适用于LLM,还适用于其他各种API工具。这意味着,如果你没有相应平台的密钥,你将无法使用其服务,特别是当你依赖像OpenAI这样的第三方平台时,保护密钥的安全并确保其不被泄露是非常关键的。在本文中,代码示例中使用了3种密钥策略。本节将以OpenAI平台为例,详细说明如何获取和使用密钥。尽管各个平台可能有所不同,但其密钥获取和使用方法大致相似。你可以查看第三方平台的官方文档或教程,通常会提供详细的步骤和示例。
获取开发密钥在开始使用OpenAI的API之前,你需要先注册一个OpenAI账户并获取API密钥。以下是获取密钥的步骤:访问OpenAI官方网站,如果你还没有账户,请点击“注册”并按照提示完成注册过程;登录你的账户,跳转到“我的”“API Keys”部分,你可以看到你的API密钥,或通过一个“+”选项来生成新的密钥;复制密钥并将其保存在一个安全的地方,确保不要与他人分享或公开你的密钥。
3种使用密钥的方法
- 方法1:直接将密钥硬编码在代码中。这是最直接的方法,但也是最不安全的。直接在代码中提供密钥的示例如下所示:
# 硬编码传参方式
openai_api_key="填人你的密钥"
from langchain.llms import OpenAI
11m OpenAI(openai_api_key openai_api_key)
# 或者在引人os模块后硬编码设置os的环境变量,简单地使用11m=OpenAI()来初始化类
import os
os.environ["OPENAI API KEY"]="填人你的密钥"
llm =OpenAI()
注意:这种方法的缺点是,如果你的代码被公开或与他人分享,你的密钥也可能被泄露。由于本书案例主要用于解释,因此每个需要开发密钥的代码示例都采用这种“显眼”的方式。但是推荐开发者使用方法2或者方法3。方法1通常是为了简化和说明如何使用API密钥,在教程、文档或示例代码中向用户展示如何设置和使用密钥,并不是实际应用中推荐的做法。在实际的生产环境或项目中,直接在代码中硬编码密钥是不推荐的。
- 方法2:使用环境变量。这是一种更安全的方法,你可以在你的本地环境或服务器上设置环境变量,将密钥保存为环境变量,然后在代码中使用它。例如,在Liux或macOS系统上,你可以在命令行中执行:
export OPENAI API KEY="填人你的密钥"
。当你在Python代码中初始化OpenAI类时,不需要传递任何参数,因为LangChain框架会自动从环境中检测并使用这个密钥。你可以简单地使用Ilm=OpenAIO命令来初始化类,如下所示:
from langchain.llms import OpenAI
llm OpenAI()
这样,即使代码被公开,你的密钥也不会被泄露,因为它不是直接写在代码中的。
- 方法3:使用getpass模块。这是一种交互式的方法,允许用户在运行代码时输人密钥,你可以简单地使用llm=OpenAI()命令来初始化类,如下所示:
import os
import getpass
os.environ ['OPENAI API KEY']=getpass.getpass ('OpenAI API Key:')
from langchain.llms mport OpenAI
llm =OpenAI()
当你运行这段代码时,它会提示你输人OpenAI API密钥。这种方法的好处是,密钥不会被保存在代码或环境变量中,而是直接从用户那里获取。管理和使用密钥是一个重要的任务,需要确保密钥的安全。上述3种方法提供了不同的密钥使用方式,你可以根据自身需求和安全考虑选择合适的方法。无论选择哪种方法,都要确保不要公开或与他人分享你的密钥。
编写一个取名程序
在LLM应用开发领域,LangChain为开发者带来了前所未有的可能性。通过编写一个取名程序,你将对LangChain框架有一个初步的了解。安装和基础配置首先,为了能够顺利进行开发工作,需要确保计算机上安装了相应的Python包。开发者可以通过以下命令轻松完成安装:pip install openai langchain每一个与API交互的应用都需要一个API密钥。开发者可以创建一个账户并获取密钥,为了保障API密钥的安全,最佳实践是将其设置为环境变量:export OPENAI_API_KEY="你的API密钥”
但是,如果开发者不熟悉如何设置环境变量,也可以直接在初始化模型包装器OpenAI时传人密钥:
from langchain.llms import OpenAI
llm=OpenAI(openai_api_key="你的API密钥")
编写取名程序有了这些基础设置,接下来就可以利用LLM进行实际的编程工作了。想象一下,有一个程序可以基于用户的描述来为公司、产品或项目提供创意取名建议。比如,当输人“为一家生产多彩袜子的公司起一个好名字”时:
llm.predict("
What would be a good company name for a company that makes"
"colorful socks?"
)
Feetful of Fun这个名字听起来不错。如此,一个简洁的、能提供创意取名建议的程序就诞生了。
LangChain表达式
LangChain秉持的核心设计理念是“做一件事并把它做好”。这种设计理念强调,每一个工具或组件都应该致力于解决一个特定的问题,并能够与其他工具或组件集成。在LangChain中,这种设计理念的体现是,它的各个组件都是独立且模块化的。例如,通过使用管道操作符“”,开发者可以轻松地实现各个组件链的组合,开发者可以像说话一样编写代码,“直接”和“简洁”就是LangChain表达式的精髓所在。这种表达式不仅使得代码结构更为清晰,还让编程的方式更加接近自然语言的表达,为开发者提供了更为直观和顺滑的编程体验。
考虑到LangChain的目标是构建LLM应用,因此,开发者可以轻松地利用其提供的组件,如PromptTemplate、ChatOpenAI和OutputParser,为LLM应用创建自定义的处理链。例如,基于StrOutputParser,开发者可以轻松地将LLM或Chat Model输出的原始格式转换为更易于处理的字符串格式。以下代码示例展示了LangChain表达式的实际应用:
from langchain.prompts import ChatPromptTemplate
from langchain.chat models import ChatopenAI
from langchain.schema.output parser import StrOutputParser
# 实例化提示词模板和聊天模型包装器
prompt =ChatPromptTemplate.from template("tell me a joke about (topic)")
model=ChatopenAI(openai_api_key="你的API密钥")
# 定义处理链
chain =prompt I model I StrOutputParser (
response =chain.invoke ({"foo":"bears"))
print(response)
# 输出:"why don't bears wear shoes?\n\nBecause they have bear feet!"
此外,LangChain的另一个关键是流水线处理。在软件开发中,氵流水线处理是一种将多个处理步骤组合在一起的方法,其中每个步骤的输出都是下一个步骤的输人。这种设计不仅简化了LLM应用开发流程,还确保了输出的高效性和可靠性。开发者们在使用LangChain构建LLM应用时,不仅可以利用其组件化的设计优势,还可以确保应用具有较高的灵活性和可扩展性,这些都是现代LLM应用开发中的关键要素。注意,使用管道操作符进行链式调用(即prompt|model|StrOutputParser()需要新版本的LangChain仓库支持,开发者们请务必将LangChain升级到最新版本。
为了帮助开发者更好地理解和使用LangChain表达式,接下来的部分将详细介绍LangChain中的一些常见表达式。提示词模板+模型包装器,提示词模板与模型包装器的组合构成了最基础的链组件,通常用在大多数复杂的链中。复杂的链组件通常都包含提示词模板和模型包装器,这是与LLM交互的基础组件,可以说缺一不可。请看以下示例:
from langchain.prompts import ChatPromptTemplate
from langchain.chat models import ChatopenAI
# 实例化提示词模板和聊天模型包装器
prompt =ChatPromptTemplate.from template ("tell me a joke about (topic)")
model=ChatOpenAI(openai_api_key="你的API密钥")
# 定义处理链
chain =prompt I model
# 调用处理链
response =chain.invoke ({"foo":"bears"))
print (response)
# 输出:AIMessage(content='Why don\'t bears use cell phones?\n\n
Because they always get terrible "grizzly"reception!',
additional_kwargs=(),example=False)
为了获得更加可控和有针对性的输出,确保输出的文本符合期望和需求,经常要将additional kwargs传人模型包装器。在下面给出的代码示例中,chain=,prompt|model.bind(stop=["n"])
这行代码表示,当LLM生成文本并遇到换行符n时,应该停止进一步的文本生成:
chain =prompt I model.bind(stop=["\n"])
response= chain.invoke ({"foo":"bears"})
#response:AIMessage(content="Why don't bears use cell phones?",
additional kwargs=(},example=False)
bind方法同样支持OpenAI的函数回调功能,可以将函数描述列表绑定到模型包装器上:
functions ={
{
"name":"joke",
"description":"A joke",
"parameters":{
"type":"object",
"properties":{
"setup":{
"type":"string",
"description":"The setup for the joke"
},
"punchline":{
"type":"string",
"description":"The punchline for the joke"
}
},
"required":["setup","punchline"]
}
}
}
chain =prompt I model.bind(function call={"name":"joke"),functions=
functions)
response =chain.invoke ({"foo":"bears"},config=())
# 输出response:AIMessage (content='',additional_kwargs=('function_call':
('name':joke','arguments':(\n "setup":"Why don\'t bears wear
shoes?",\n "punchline":"Because they have bear feet!"\n)')),
example=False)
提示词模板+模型包装器+输出解析器。可以在提示词模板与模型包装器的组合基础上,再增加一个输出解析器。示例如下:
from langchain.schema.output parser import StrOutputParser
chain= prompt I model I StrOutputParser()
response= chain.invoke ({"foo":"bears"),config=())
#response:"Why don't bears wear shoes?\n\nBecause they have bear feet!"
当定义一个要返回的函数时,你可能不希望进行额外的处理,而只希望直接对函数进行解析。为了满足这个需求,LangChain为OpenAI提供了一个专门的函数回调解析器,名为JsonOutputFunctionsParser。这意味着在LangChain…output_parsers下的所有内置输出解析器的类型都是可用的。此外,还可以根据自己的需要使用自定义的输出解析器:
from langchain.output_parsers.openai functions import JsonOutputFunctionsParser
chain =prompt I model.bind(
function call=("name":"joke"),
functions=functions)
| JsonOutputFunctionsParser()
)
response chain.invoke ({"foo":"bears"))
#response:('setup':"Why don't bears wear shoes?",
'punchline':'Because they have bear feet!')
多功能组合链,首先定义两个提示词模板prompt1和prompt2,分别用来询问某人来自哪个城市,以及这个城市位于哪个国家。chain1是由promptl、model和StrOutputParser组成的链,目的是根据给定的人名返回此人来自哪个城市。chain2是更复杂的链。它首先使用chainl的结果(城市),然后结合itemgetter提取的language键值,生成输入prompt2的完整问题。这个问题随后会被传递给模型,并通过StrOutputParser解析:
from operator import itemgetter
prompt1 = ChatPromptTemplate.from_template("what is the city (person)is from?")
prompt2 = ChatPromptTemplate.from template"what country is the city (city}in?respond in (language)"
chain1 promptl I model I StroutputParser (
chain2 =
{"city":chainl,"language":itemgetter("language"))
I prompt2
| model
I StrOutputParser()
chain2.invoke (("person":"obama","language":"spanish"))
当调用chain2并传递{"person'":"obama","language":"spanish"}
作为输入时,整个流程将按顺序执行,并返回最终结果:
'El pais en el que nacio la ciudad de Honolulu,Hawai,donde nacio Barack
Obama,el 44 presidente de los Estados Unidos,es Estados Unidos.'
下面我们加大难度,创建一个更复杂的组合链。先定义4个提示词模板,涉及颜色、水果、某国家国旗颜色,以及水果和国家(国旗)的颜色对应关系。chain1是一个简单的链,根据promptl生成一个随机颜色。chain2是一个复杂的链,首先使用RunnableMap和chainl来获取一个随机颜色。接下来,这个颜色被用作两个并行链的输人,分别询问此颜色的水果有什么,以及哪个国家的国旗是这个颜色的。示例代码如下:
from langchain.schema.runnable import RunnableMap
prompt1 =ChatPromptTemplate.from template ("generate a random color")
prompt2 =ChatPromptTemplate.from template ("what is a fruit of color:(color)")
prompt3 =ChatPromptTemplate.from template ("what is countries flag that has the color:{color)")
prompt4 =ChatPromptTemplate.from template ("What is the color of (fruit}and{country)")
chainl prompt1 I model I StrOutputParser()
chain2 RunnableMap(steps=("color":chain1))("fruit":prompt2 I model I StroutputParser (),"country":prompt3 I model I StroutputParser(),}I prompt4
最后,这两个并行链的返回结果(一个水果和一个国家)被用作prompt44的输人,询问这个水果和这个国家的国旗是什么颜色的。
chain2.invoke ((}ChatPromptValue(messages=[HumanMessage(content="What is the color of Afruit that has a color similar to #7E7DE6 is the Peruvian Apple Cactus (Cereusrepandus).It is a tropical fruit with a vibrant purple or violet exterior.andThe country's flag that has the color #7E7DE6 is North Macedonia.",additional_kwargs=(},example=False)])