跳到主要内容基于大语言模型开发应用接口实战指南 | 极客日志PythonAI算法
基于大语言模型开发应用接口实战指南
本文详细介绍了基于大语言模型开发应用接口的完整流程。首先分析了 LLM 的多轮对话、Zero-Shot、代码及图形理解等核心能力,并阐述了结构化与非结构化数据的区别及转换方案。接着深入讲解了 LangChain 框架的核心组件,包括 Prompt、LLM、OutputParser 及 Chain 的合成方法。通过 Azure ChatOpenAI 示例展示了如何构建情感分析应用,并提供了基于 FastAPI 的接口部署代码。此外,文章还补充了生产环境下的安全配置、环境变量管理、异常处理及性能优化等最佳实践,帮助开发者构建稳定可靠的大模型应用。
steve1 浏览 一、前言
针对自然语言处理方向,以前要开发一个情感分析、命名实体识别之类的应用是非常麻烦的。我们需要完成收集数据、清理数据、构建模型、训练模型、调优模型等一系列操作,每一步都非常耗时。如今大语言模型(LLM)的出现,极大简化了其中一些任务。LLM 像一个全能的自然语言机器,通过一些简单调整,就可以实现一些特定任务。
这些特定任务不只限于自然语言的基本任务,我们还可以实现更复杂的应用。今天我们就来详细讨论一下如何基于 LLM 开发实际的应用接口。
二、大语言模型基础
2.1 大语言模型的能力
相信大家都知道了 LLM 是怎么工作的,也就是玩文字接龙游戏。经过预训练和 RLHF(Reinforcement Learning from Human Feedback)后,LLM 具备了非常强大的自然语言理解能力和指令遵循能力。
我们可以看几个实际例子,现在假设我们是 2020 年的人类,ChatGPT 还未出现,站在这个视角下,我们才能明白 LLM 的强大。
1. 多轮对话
首先是多轮对话能力,早期要实现多轮对话需要人类的许多干预,而 ChatGPT 则可以一步到位实现多轮对话:
You:你好啊
ChatGPT:你好!有什么我可以帮你的吗?
You:我上一句说了啥
ChatGPT:你说了"你好啊",我回复了"你好!有什么我可以帮你的吗?"
那为什么可以呢?因为 LLM 的训练方式是文字接龙,如果我们的训练数据中本身就有对话数据,那 LLM 能实现对话功能也不足为奇。而多轮对话则是每次把历史对话也作为输入,因为 LLM 已经用多轮对话的数据训练了,那他自然可以实现多轮对话。比如:
You:你好啊
ChatGPT:你好!有什么我可以帮你的吗?
You:我上一句说了啥
2. Zero-Shot
Zero-Shot 的意思是零样本,就是在没有额外数据的情况下,完成某个任务。以情感分析为例,我们可以用下面的方式和 LLM 对话:
You:'我很喜欢这部电影'包含的情感是:
ChatGPT:积极
在 LLM 预训练阶段,如果我们用下面模板生成数据交给 LLM 训练:
'{{ sentence }}'包含的情感是:{{ label }}
3. 代码能力
起初我认为代码生成工具无非就是查数据库,而实际体验后,却发现工具真的对代码有自己的理解。比如我们可以让工具写出 Sleep 排序算法的代码,而这个排序算法实际并不存在。我们只需要描述 Sleep 排序的逻辑即可。
- 根据注释生成代码
- 输入代码,让其纠错
- 输入代码,让其简写或优化
- 输入代码,让其修改为其它语言版本
在 2020 年的我们面前,上面这些功能都不是近十年可以实现的,但是第二年就已经可以做到了。
4. 图形理解
LLM 有一个惊人的能力,就是字符图形理解能力。这里我们不讨论 VisionLLM。比如我们可以和 LLM 下棋:
You:我们来玩一个游戏,在 3x3 的网格里面,谁先连成连续三个谁赢。我先手:
x | |
| |
| |
ChatGPT:好的,我也选择一个空格:
x | |
|o|
| |
而原因的话,可能是某群无聊的人正好使用上面的方式玩游戏,而这些数据正好被 OpenAI 爬取了。
2.2 结构化和非结构化
LLM 还有需要其它能力,这里不再列举,我们来讨论一下将 LLM 接入应用的前提。
对于程序员来说,结构化数据是非常有用的。我们编写各种 api 接口返回的数据都遵循一定格式,这种我们称为结构化数据。比如天气接口,下面是一种可能的形式:
{
"city": "广州",
"temperature": "34"
}
当前端程序员拿到这个接口后,可以很容易将数据展现出来。
等等。这种数据我们称为非结构化数据,如果我们返回的输入是上面的形式,那么前端确定不了怎么展示。
在非结构化数据中,我们包含了结构化数据里的信息,但是组织方式不一样。或许可以通过硬编码,编写一堆 if else 把 city 和 temperature 提取出来,然后把非结构化数据转换成结构化数据,但是这样太麻烦了,而且实际情况要更为复杂。
如果现在有一个工具,可以将非结构化数据转换成结构化数据,那事情就简单了,而 LLM 就是这样一个工具。
You:帮我把:'广州今天的温度是 34'转换成 json 数据,包含 city 和 temperature 两个键。只返回 json 内容。
ChatGPT:{
"city": "广州",
"temperature": 34
}
You:帮我把:'今天广州 34°'转换成 json 数据,包含 city 和 temperature 两个键。只返回 json 内容。
ChatGPT:{
"city": "广州",
"temperature": 34
}
三、LangChain 框架实战
LangChain 是一个用来创建 LLM 应用的模块,我们可以使用 LangChain 创建各种 Chain,完成各式各样的任务。
3.1 Chain 的主要部分
我们要组建一个 Chain,需要知道几个主要模块。Chain 的最简单组合就是单纯一个 LLM,它可以接收文本输入,输出文本。
而在上面的内容中,我们发现了一些特点,即输入内容有一部分是固定的。比如在情感分析部分,我们的输入可以提炼成:
其中 sentence 是要替换的内容。在非结构化转结构化部分,我们的输入可以提炼成:
帮我把:'{{ sentence }}'转换成 json 数据,包含 city 和 temperature 两个键。只返回 json 内容。
这种提炼出来的部分,我们就叫做 PromptTemplate,或者叫 Prompt。这样就引出了 Chain 的第二个部分,Prompt。
在非结构化转结构化部分,我们得到的结构都是非常标准的 json。但是实际情况并没有这么理想,可能会出现下面这些情况:
多余的字符:
```json
{
....具体内容
}```
多余的解释:
好的,下面是转换后的结果
{
....具体内容
}
错误的字段
总之并不是每一次结果都如我们预想。这个怎么解决呢?最简单的办法就是拿到输出后,我们用正则匹配,拿到最终结果。而对数据结果再次操作,提取内容的这部分叫做 OutputParser。
这样 Chain 的三个主要部分都出来了:Prompt、LLM、OutputParser。
3.2 Prompt
pip install langchain
pip install langchain_community
pip install langchain_openai
构建 Prompt 主要有 PromptTemplate 和 ChatPromptTemplate 两个类,前者针对基本的 LLM,后者则针对 ChatModel。这里我们使用 ChatPromptTemplate。
在 LLM 中,通常有三种消息,分别是:system、assistant、human。其中 system 设定该模型在当前整个会话中的情况。比如我们希望当前模型是一个心理医生,我们就可以把 system 设定为:"你是一个心理医生…"。
在代码上,我们使用 ChatPromptTemplate.from_messages 方法完成,代码如下:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个心理医生..."),
("human", "心情不好...")
])
如果我们向构造一个情感分析的应用,那么我们可以把 prompt 改成:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个情感分析程序,不管用户输入什么,你都返回积极或消极。"),
("human", "{sentence}")
])
print(prompt.invoke({'sentence': '心情不好'}))
在代码里面,我们添加了 {sentence} 用来占位,然后我们调用 prompt.invoke({'sentence', '心情不好'}),可以把 prompt 里面的 sentence 标记,替换成后面的内容,运行后输入如下:
messages=[SystemMessage(content='你是一个情感分析程序,不管用户输入什么,你都返回积极或消极。'), HumanMessage(content='心情不好')]
3.3 LLM
接下来是 LLM,在前面提到两类 Prompt,模型也有两类。我们用 ChatPromptTemplate 构建 prompt,所以我们使用 ChatModel。
国内比较方便访问 GPT 的方式之一是 Azure,在 LangChain 里面提供了对应的实现,我们可以通过下面的方式创建一个 ChatModel:
from langchain_openai import AzureChatOpenAI
deployment_name = "gpt-35-turbo-16k"
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
print(llm.invoke('你好啊'))
这里使用 AzureChatOpenAI,填写对应的参数创建 llm。这里同样调用 invoke 方法进行推理,输出结果如下:
content='你好!有什么我可以帮助你的吗?'
现在可以把 prompt 和 llm 结合起来,只需要分别执行 prompt 的 invoke 和 llm 的 invoke 即可:
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
deployment_name = "gpt-35-turbo-16k"
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个情感分析程序,不管用户输入什么,你都返回积极或消极。"),
("human", "{sentence}")
])
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
print(llm.invoke(
prompt.invoke({'sentence': '心情不好'})
))
从上面的结果,我们可以很自然地觉得,模型上面的 Chain 只会返回'积极'或'消极',但是某天如果 Chain 背刺你,给你返回一个'消极的。'或者'消极。',那我们可能会因为这个意向不到的回复调试半天程序。
所以下面我们就要为我们的 Chain 添加一个 OutputParser 了。
3.4 OutputParser
为了从 LLM 的输出中解析出我们真正想要的内容,我们还需要有一个 OutputParser。我们可以自己使用正则等方式来实现 OutputParser,不过这里我们使用另一种简单的方式。这里还是以天气为例,首先定义一个 Model:
from langchain_core.pydantic_v1 import BaseModel, Field
class Weather(BaseModel):
city: str = Field(default=None, description="城市")
temperature: str = Field(default=None, description="温度")
在 Model 里面,定义了数据的格式,然后我们只需要使用下面一句,就可以给 llm 添加一个 OutputParser:
chain = llm.with_structured_output(schema=Weather)
print(chain.invoke('你好啊'))
这里我们用了一个离谱的例子,给 chain 发送'你好啊',而且没有像前面 Prompt 一样添加一些限制,那代码能顺利运行吗?下面是输出结果:
city='北京' temperature=None
没有报错,而且正确输出了一部分内容。由此可以知道,上述方式并不是简单的正则匹配。如果查看源码可以发现,其实是使用了 function_calling 和 json_mode:
@beta()
def with_structured_output(
self,
schema: Optional[_DictOrPydanticClass] = None,
*,
method: Literal["function_calling", "json_mode"] = "function_calling",
include_raw: bool = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, _DictOrPydantic]:
另外这个功能目前是 beta 状态,使用时需要注意。
3.5 合成 Chain
最后我们要做的就是组成一条 Chain 了,这里不需要自己一直调用 invoke 了,而是使用'|'组成 Chain,然后调用一次 invoke 即可:
chain = prompt | llm.with_structured_output(schema=Weather)
chain.invoke({"input": "北京"})
四、应用接口部署与最佳实践
最后我们部署一个接口,这里使用 FastAPI,代码如下:
import uvicorn
from fastapi import FastAPI
from enum import Enum
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from fine_tunning.configs import endpoint, api_key
class SentimentEnum(Enum):
positive = 'positive'
negative = 'negative'
normal = 'normal'
class Weather(BaseModel):
sentence: str = Field(default=None, description="句子")
sentiment: SentimentEnum = Field(default=None, description="情感")
deployment_name = "gpt-35-turbo-16k"
prompt = ChatPromptTemplate.from_messages([
("system",
"你是一个情感分析程序,不管用户输入什么,请返回包含 sentence 和 sentiment 两个键的 json 格式数据。只要 json 内容,不要其它内容"),
("human", "{sentence}")
])
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
chain = prompt | llm.with_structured_output(schema=Weather)
app = FastAPI()
@app.get('/sentiment-analysis')
async def sentiment_analysis(sentence: str):
try:
result = dict(chain.invoke({'sentence': sentence}))
return result
except Exception as e:
return {"error": str(e)}
if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=8000)
http://localhost:8000/sentiment-analysis?sentence=这电影一般般
{"sentence":"这电影一般般","sentiment":"normal"}
4.1 安全配置与环境变量管理
在实际生产环境中,直接将 API Key 硬编码在代码中是非常危险的行为。建议使用环境变量来存储敏感信息,例如 AZURE_OPENAI_API_KEY 和 AZURE_ENDPOINT。
可以使用 python-dotenv 库加载 .env 文件:
from dotenv import load_dotenv
load_dotenv()
import os
api_key = os.getenv("AZURE_OPENAI_API_KEY")
endpoint = os.getenv("AZURE_ENDPOINT")
4.2 异常处理与日志记录
大模型服务可能存在网络波动或超时情况。在 FastAPI 接口中,应当增加适当的异常捕获机制,避免服务崩溃。
同时,建议引入日志系统(如 logging),记录请求的输入和输出,以便后续排查问题和监控模型表现。例如,记录哪些输入导致了错误响应,或者分析 Token 消耗情况。
4.3 性能优化建议
- 缓存机制:对于相同的输入,可以考虑使用 Redis 等缓存中间件,减少重复调用大模型的开销。
- 异步处理:如果接口响应时间要求不高,可以将 LLM 调用放入后台任务队列(如 Celery),避免阻塞主线程。
- 流式输出:对于长文本生成场景,支持 SSE(Server-Sent Events)流式传输可以提升用户体验。
通过上述步骤,我们可以构建一个稳定、安全且易于维护的大模型应用接口。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online