langchain学习笔记之langserve服务部署
langchain学习笔记之langserve服务部署
引言
本节将介绍 LangServe \text{LangServe} LangServe服务部署。
LangServe \text{LangServe} LangServe简单介绍
LangServe
\text{LangServe}
LangServe帮助开发者将langchain
可运行的chain
部署为REST API
,该库底层使用FAST API
并使用pydantic
进行数据验证。
关于pydantic
,它是一个
Python
\text{Python}
Python中用于数据验证和数据解析的第三方库,并且可以从模型中生成
JSON
\text{JSON}
JSON架构,并提供自动生成文档等功能,从而轻松与其他模型进行集成。
安装过程
- 对于客户端与服务器, LangServe \text{LangServe} LangServe的安装指令
pip install --upgrade "langserve[all]"
单独对于客户端或者服务器端代码,则执行:
pip install "langchain[client]"
pip install "langchain[server]"
-
LangChain CLI
\text{LangChain CLI}
LangChain CLI安装:该工具可以快速启动
LangServe
项目。安装指令:
pip install -U langchain-cli
安装完毕后,在terminal
中输入langchain
,可以查看其相关操作:
在项目路径下,使用指令:
langchain app new [app_name] # app_name 为想要设置的app名称
在当前路径下会生成一个文件夹:
接下来,安装pipx
:
pip install pipx
将pipx
添加到环境变量,执行完该操作后,需重启
PyCharm
\text{PyCharm}
PyCharm:
pipx ensurepath
正确的返回结果:
Success! Added C:\Users\dell\.local\bin to the PATH environment variable.
Consider adding shell completions for pipx. Run 'pipx completions' for instructions.
You will need to open a new terminal or re-login for the PATH changes to take effect. Alternatively, you can source your shell's config file with e.g. 'source ~/.bashrc'.
Otherwise pipx is ready to go! ✨ 🌟 ✨
接下来,使用pipx
安装poetry
:
pipx install poetry
值得庆幸的是,pipx
同样会使用PyPI
作为包的来源,因而可以使用镜像源进行安装。例如:
pipx install poetry -i https://mirrors.aliyun.com/pypi/simple/
正确的返回结果:
installed package poetry 2.0.1, installed using Python 3.10.1
These apps are now globally available
- poetry.exe
done! ✨ 🌟
同样,在terminal
中输入poetry
,可以查看其相关操作:
接下来,使用poetry
指令安装相关库。根据上一节使用的
Qwen
\text{Qwen}
Qwen模型,这里安装的是与
Qwen
\text{Qwen}
Qwen模型相关的库:
先装着,用不用得到以后再说~
poetry add langchain
poetry add langchain-community
poetry add langchain-core
poetry add dashvector
如果安装成功,刚才安装的APP
文件夹中的pyproject.toml
文件中,会将安装库的版本显示在内:
[tool.poetry.dependencies]
python = "^3.11"
uvicorn = "^0.23.2"
langserve = {extras = ["server"], version = ">=0.0.30"}
pydantic = ">=2.7.4"
langchain = "^0.3.18"
langchain-community = "^0.3.17"
langchain-core = "^0.3.35"
dashvector = "^1.0.18"
一个小坑:上面部分初始状态下的结果展示如下:
[tool.poetry.dependencies]
python = "^3.11"
uvicorn = "^0.23.2"
langserve = {extras = ["server"], version = ">=0.0.30"}
pydantic = "<2"
其中python
版本我们按照它的要求,将当前conda
环境内的版本升级到了3.11.11
;但在使用poetry
安装上述包时,依然出现错误。主要原因是:当前的langchain
版本0.3.18
与上述pydantic = "<2"
产生版本冲突,需要手动将上面的pydantic
版本改成与langchain
相匹配的版本,我这里设置的是pydantic = ">=2.7.4"
。
设置环境变量:
关于
Qwen
\text{Qwen}
Qwen模型,依然要导入申请的dashscope-api-key
:
export DASHSCOPE_API_KEY = "sk-..." # 自行申请的api-key
若将api-key
写死在本机中(计算机右键属性,高级系统设置,环境变量,添加api-key
),此条可以忽略
启动应用。在terminal
中使用poetry
执行如下指令:
poetry run langchain serve --port=8000 # 端口号可自行分配
需要注意的是,需要在pyproject.toml
所在文件夹位置执行该指令,即上步骤中创建app
文件夹的路径内。正确返回结果如下:
打开上述链接,得到如下结果:
这里包含创建的routes
以及相应的功能接口。
示例应用
想要部署一个
Qwen
\text{Qwen}
Qwen聊天模型,在使用langchain app new [app_name]
指令时,在app
文件夹中,自动生成一个server.py
文件:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from langserve import add_routes
app = FastAPI()
@app.get("/")
async def redirect_root_to_docs():
return RedirectResponse("/docs")
# Edit this to add the chain you want to add
add_routes(app, NotImplemented)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
可以在该界面中对app
自身参数信息进行完善。例如:
app = FastAPI(
title="langchain server",
version="0.1.0",
description="A server for running Langchain with FastAPI",
)
对应的效果如下:
调用模型接口实现交互
第一步:在server.py
文件中,添加一个routes
:
llm = Tongyi(
model_name="tongyi-7b-chinese",
temperature=0.5,
max_tokens=100)
add_routes(
app,
llm,
path="/tongyi-7b-chinese")
请记住这个path
,在创建routes
完毕之后,若重新执行server.py
或者在terminal
执行指令poetry run langchain serve --port=8000
,都可以看见一个playgound
被生成:
进入该路径:http://localhost:8000/tongyi-7b-chinese/playground/
可以得到一个在线的基于tongyi-7b-chinese
模型的问答系统~
言归正传~ 创建一个client.py
文件,代码如下:
from langserve import RemoteRunnable
p = 8000
model_loc = "tongyi-7b-chinese"
# http://127.0.0.1:8000/docs#/tongyi-7b-chinese/
def get_remote_runnable_url(port_input, model_loc_input):
url_output = 'http://localhost:{}/{}/'.format(str(port_input), model_loc_input)
return url_output
tongyi = RemoteRunnable(
get_remote_runnable_url(port_input=p, model_loc_input=model_loc)
)
引入一个类RemoteRunnable
来远程调用模型,这里的model_loc
之前add_routes
中的path
相同。此时变量tongyi
可看作是一个模型,在构建chain
的过程中,调用RannableMap
类对变量进行描述:
from langchain.schema.runnable import RunnableMap
chain = prompt | RunnableMap(
{
"tongyi": tongyi
}
)
其余部分,如prompt
,chain
的调用均正常。client.py
完整代码如下:
from langchain.schema.runnable import RunnableMap
from langchain_core.prompts import ChatPromptTemplate
from langserve import RemoteRunnable
p = 8000
model_loc = "tongyi-7b-chinese"
# http://127.0.0.1:8000/docs#/tongyi-7b-chinese/
def get_remote_runnable_url(port_input, model_loc_input):
url_output = 'http://localhost:{}/{}/'.format(str(port_input), model_loc_input)
return url_output
tongyi = RemoteRunnable(
get_remote_runnable_url(port_input=p, model_loc_input=model_loc)
)
prompt = ChatPromptTemplate(
[
("system", "你的名字是静静的喝酒,你是一个喜欢做科普的助手"),
("user", "简单介绍一下{input},字数限制:200")
]
)
chain = prompt | RunnableMap(
{
"tongyi": tongyi
}
)
response = chain.stream({
"input": "林徽因"
})
for chunk in response:
print(chunk["tongyi"], end="", flush=True)
返回结果如下,通过流式调用stream
生成出的结果:
林徽因(1904-1955),原名林徽音,中国著名建筑师、诗人、作家。她出生于浙江杭州,父亲林长民是民国时期的重要官员。林徽因自幼聪慧过人,16岁赴英留学,后转入美国宾夕法尼亚大学学习建筑。
她与梁思成共同致力于中国古代建筑的研究和保护工作,参与了多处重要古建筑的考察与测绘。同时她也是中国第一位女性建筑学家,在建筑设计领域有着卓越贡献。
林徽因在文学上也有很高造诣,著有散文集《你是人间四月天》等作品,诗文优美动人。她的才华横溢,不仅体现在专业领域,在社交场合也备受尊敬,是中国现代文化史上一位杰出的女性。
Process finished with exit code 0
当然,也可以使用invoke
等其他方式进行调用,这里不再展示。
使用 Requests \text{Requests} Requests方式进行交互
相比于使用Runnable
调用模型进行交互,还可以使用requests
直接进行交互,这种交互方式的特点是:调用部分客户端代码异常简单。仅需要知道url
路径以及相应格式的输入即可。创建一个新的文件:client_req.py
,分别以invoke
调用与stream
调用为例:
在此之前,需要在server.py
中添加一个新的routes
,并创建一个新的path
:
prompt = PromptTemplate.from_template("请介绍一下{topic},200字")
add_routes(
app,
prompt | llm,
path="/tongyi-7b-chinese-chain"
)
和调用模型接口交互的操作相比,主要区别是:将prompt
,chain
的部分交给了server.py
去做。
invoke
调用。对应代码如下:
response_invoke = requests.post(
url="http://localhost:8000/tongyi-7b-chinese-chain/invoke",
json={
"input": {
"topic": "林徽因"
}
}
)
可以发现:url
路径中多了\invoke
,这是langchain serve
提供的接口:
将其展开,可以得到要求输入的格式以及输出格式:
上面的json
格式就是基于该输入格式构建的。输入结果也为json
格式。基于上述输入,输入结果如下。由于格式已经确定,完全可以获取更精确的信息(例如:只展示output
部分),这里不再赘述。
print(response_invoke.json())
{'output': '林徽因(1904年6月10日-1955年4月1日),原名林徽音,中国著名建筑师、诗人、作家。她出生于福建闽侯,在文学和建筑领域均有所建树。\n\n林徽因是第一位女性建筑学家,曾参与设计了中华人民共和国国徽和人民英雄纪念碑。她的建筑设计融合了传统与现代元素,独具匠心。同时,她也是新月派诗人群体中唯一的女性成员,著有《你是人间四月天》等脍炙人口的诗作。其作品风格清新婉约,充满灵性,表达了对生活的热爱以及对理想的追求。\n\n林徽因以其卓越才华和独特魅力,在近现代中国文化史上留下了浓墨重彩的一笔。', 'metadata': {'run_id': '093d5777-d19c-4bc5-9b53-d1e508765661', 'feedback_tokens': []}}
stream
调用:同上,url
路径中同样包含\stream
接口,以及输入、输出格式。对应代码表示如下:
response_stream = requests.post(
url="http://localhost:8000/tongyi-7b-chinese-chain/stream",
json={
"input": {
"topic": "林徽因"
}
}
)
比较特殊的是,stream
调用返回的结果类型是string
,在处理数据上,例如:将返回结果有效信息提取出来,需要消耗一些代码:
for l in response_stream.iter_lines():
line = l.decode("utf-8")
if line.startswith("data:"):
if "run_id" in line:
continue
else:
print(line[len("data: ") + 1:-1], end="", flush=True)
返回结果如下:
林徽因(1904年6月10日-1955年4月1日),原名林徽音,中国著名建筑师、诗人、作家。她出生于福建闽侯,成长于一个书香门第,自幼聪慧过人。1920年随父游历欧洲,深受西方文化影响。1924年赴美留学,主修建筑学,后与梁思成结为夫妇,共同致力于中国古代建筑的研究和保护。\n\n林徽因不仅在建筑领域成就斐然,还是一位才华横溢的文学家。她的诗歌清新脱俗,散文优美动人,代表作有《你是人间四月天》等。此外,她积极参与社会活动,为中国古代建筑艺术的传承与发展做出了重要贡献。林徽因的一生短暂而辉煌,她的思想和作品至今仍被广泛传颂。
client_req.py
完整代码如下:
import requests
import json
# invoke_method
response_invoke = requests.post(
url="http://localhost:8000/tongyi-7b-chinese-chain/invoke",
json={
"input": {
"topic": "林徽因"
}
}
)
print(response_invoke.json())
# stream_method
response_stream = requests.post(
url="http://localhost:8000/tongyi-7b-chinese-chain/stream",
json={
"input": {
"topic": "林徽因"
}
}
)
print("stream result..")
# 只是一个相应结果,200表示成功;422表示失败
print(response_stream)
for l in response_stream.iter_lines():
line = l.decode("utf-8")
if line.startswith("data:"):
if "run_id" in line:
continue
else:
print(line[len("data: ") + 1:-1], end="", flush=True)
一个小坑:若修改client_req.py
文件(例如修改topic
或者其他地方)后再执行代码时,可能出现这样的错误:
requests.exceptions.ChunkedEncodingError: Response ended prematurely
观察一下langchain serve
的执行情况:
报错的最终原因是:client_req.py
存在修改,并重新加载;从而导致ASGI可调用对象返回但未完成响应。再执行一次就好了~
综上,两种交互方式介绍完毕。两者各有特色:
- 模型调用交互:仅使用
Runnable
远程调用模型,如prompt
,chain
需要client
自己构建,但这些步骤偏向简单,调用相关类就可以了; request
交互:仅需要知晓url
以及数据输入格式即可;但输出格式需要手动处理(invoke
,stream
等其他调用方式),比较乐观的是,输出格式也是确定的,在langchain serve
中的相应接口中可以查到;上面的小坑也只有在request
交互中出现,因为每一次修改代码,都会影响langchain serve
的执行,但模型调用交互不会出现这种情况原因是:修改代码并不影响模型的调用,除非去修改模型- -~
个人更偏向于模型调用交互。
附: server.py \text{server.py} server.py完整代码
至此,代码就齐了,大家一起努力呀~
from fastapi import FastAPI
from langchain_community.llms import Tongyi
from fastapi.responses import RedirectResponse
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langserve import add_routes
app = FastAPI(
title="langchain server",
version="0.1.0",
description="A server for running Langchain with FastAPI",
)
llm = Tongyi(
model_name="tongyi-7b-chinese",
temperature=0.5,
max_tokens=100,
)
@app.get("/")
async def redirect_root_to_docs():
return RedirectResponse("/docs")
# Edit this to add the chain you want to add
add_routes(
app,
llm,
path="/tongyi-7b-chinese"
)
prompt = PromptTemplate.from_template("请介绍一下{topic},200字")
add_routes(
app,
prompt | llm,
path="/tongyi-7b-chinese-chain"
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)