LV010-AI的API调用
这一部分主要是 API 的调用、参数、对话与状态管理。
一、概述
AI 助手应该是什么样的?
(1)懂我们的意图
(2)深入聊天
(3)记得我们的喜好
这样的应用背后,我们需要解决三个核心问题:
- 如何让它“听话”并且”说的好“?参数控制
- 如何让“它不健忘”,能连贯的对话?对话管理
- 如何让它“认识我们”,提供个性化服务?状态管理
二、起点:API 调用
1. 什么是 API 调用
API(Application Programming Interface,应用程序编程接口),它属于一个中间人的角色。模型本身只是一个文件或程序,API 是一种让它能够被远程调用的 通信方式。
下面是 ai 给我举得一个例子:
- 你(你的应用程序):一位想去吃饭的顾客。你想吃一道复杂的菜,比如“佛跳墙”,但你不知道怎么做,也没有厨房和食材。
- 大模型(比如 GPT-4、文心一言等):一个设备顶级、厨艺精湛的后厨。它拥有所有昂贵的厨具(成千上万的 GPU)、珍贵的食材(海量数据)和顶尖的厨师(复杂的算法)。但这个后厨不对外开放,你不能直接进去自己动手。
- API 餐厅的服务员和菜单。
(1)菜单(API 文档):告诉你这家厨房能做什么菜(比如“文本总结”、“代码生成”、“翻译”、“问答”),以及你需要提供什么信息(比如你想总结的“原文是什么?”)。
(2)服务员(API 服务):你按照菜单点餐(通过代码发送一个请求),服务员会把你的订单准确无误地送到后厨。厨房做好菜后,服务员再把香喷喷的“佛跳墙”(模型的回答)端回给你。
所以,大模型的 API 就是一个标准化的“通信窗口”或“服务协议” 。我们的程序不需要知道大模型内部是如何工作的,只需要按照 API 文档规定的格式(比如发送一段 JSON 文本),向一个指定的网络地址(API 端点)发送请求,就能得到大模型返回的结果。
2. 什么时候需要 API?
那我们要是把大模型部署在本地,还需要这个 api 吗?什么时候才需要 api 呢?
本地部署的模型本身不需要依赖外部的 API,但为了让它能被方便地使用,我们通常会为它“创建”一个本地的 API 服务。我们可以用一个简单的比喻来理解:
- 本地部署的模型:就像厨房里的 全套厨具和食材。
- 模型文件:就是菜谱。
- 为模型创建本地 API:相当于在家里定了一个 点餐和上菜的规则。
(1)不需要 api 的情况:直接调用(“自己下厨”)
这种方式下,我们和模型是“零距离”接触。比如我们现在写了一个 Python 脚本,用来处理本地文件。脚本直接加载模型,处理数据,然后输出结果。整个过程中,只有我们这个脚本在和模型互动。
优点:简单、快速、延迟极低。因为没有网络通信的开销。
缺点:不灵活。这个模型能力被“锁死”在这个脚本里了,其他程序或用户无法使用它。
(2)需要 API:服务化部署(“在家开家庭餐厅”)
这种方式下,我们为模型创建一个服务,让它能接受来自各方的请求。比如我们想开发一个带界面的 本地 Web 应用(一个桌面版的 AI 聊天助手?)。然后还想让公司内网的 其他同事或程序 也能使用我们服务器上的这个模型。
这个时候我们需要使用像 FastAPI、Flask 这样的框架,写一个简单的程序。这个程序会加载模型,并监听一个网络端口(如 http://localhost:8000)。其他程序(如的网页前端、其他同事的脚本)就可以像访问网站一样,向这个地址发送请求来使用模型。
优点:灵活、可共享、易于集成。模型变成了一个标准化的服务,可以被任何能发送网络请求的程序调用。
缺点:引入了微小的网络延迟,部署稍微复杂一点。
Tips:现在有很多工具可以帮我们一键把本地模型变成 API 服务,极大简化了过程:
- Ollama:部署和运行开源模型的最流行工具,运行后自动提供一个本地 API。
- LM Studio:图形化界面工具,同样在启动模型后提供本地 API 端点。
- vLLM:一个高性能的推理引擎,专为生产环境设计,核心就是提供 API 服务。
使用这些工具,我们基本上只需要执行一条命令,它们就会在本地(如
http://localhost:11434)启动一个兼容 OpenAI API 格式的服务,让我们可以像调用 GPT-4 的 API 一样调用我们自己的本地模型。
3. API 调用的本质
API 调用并不是简单的“请求——响应”,而是一次“意图的编码和解码的过程”。
- 编码意图:把人类语言和指令转换为结构化数据,例如,JSON 格式。
- API 传输:通过标准协议传递编码后的意图。
- 解码响应:将 AI 的输出换回可理解的形式。
4. 与 AI 对话的“信使”
完成 api 调用需要下面几个内容:
- Endpoint(服务入口):API 的网络地址,指向一个特定的 ai 服务。相当于门牌号。
- Headers(握手协议):身份验证与数据格式建立安全的通信通道。相当于身份证。
- Payload(结构化信件):指令、参数和上下文,承载全部的“意图”。相当于包裹。
4.1 Endpoint(服务入口)
去哪里找 AI?上面已经介绍了 API 调用,那肯定是要找一个 API 的网络地址(URL),例如 Deepseek 官网中为我们提供的 base_url(可以去接口文档查):
https://api.deepseek.com/v1Tips:
官网也会有调用的示例:
python# Please install OpenAI SDK first: `pip3 install openai` import os from openai import OpenAI client = OpenAI( api_key=os.environ.get('DEEPSEEK_API_KEY'), base_url="https://api.deepseek.com") response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "You are a helpful assistant"}, {"role": "user", "content": "Hello"}, ], stream=False ) print(response.choices[0].message.content)申请好 Deepseek 的 API Key 后就可以使用上面的 Python 脚本访问 Deepseek API,上面的例子是非流式输出,可以吧 stream 设置为 true 来使用流式输出。
这个 API 的 作用:指向特定的 ai 服务(例如文本生成、图像理解等)。
4.2 Headers(握手协议)
这个 Headers 会包含 Authorization(认证)和 Content-Type(内容类型)。
Authorization 是用来证明“你是你”,也就是进行身份验证,它会携带 API key(一般是
Authorization: Bearer YOUR_API_KEY)。这个就是我们在对应的大模型服务提供商哪里申请的。但也不是所有的大模型服务都需要,一些在线的大模型,需要对外服务的大模型为了建立安全、规范的通信通道,一般都是需要身份验证,但是有一些本地的大模型服务,例如后面会使用的 Ollama 运行本地大模型的时候,就可以不用 API key。Content-Type 内容类型,一般是
Content-Type: application/json,它用来声明数据格式,是我们和大模型沟通的语言规范,例如 JSON 格式。
4.3 Payload(结构化信件)
可以先来看一段 json 格式的 payload:
{
"model": "deepseek-chat",
"messages": {
{"role": "user", "content": "Hello"},
},
"temperature": 0.7
}payload 就是把指令、参数、上下文打包成大模型可读的格式,一般是 JSON。它的作用就是承载我们要传达的全部“意图”。
4.4 总结
说明:下面的总结由 AI 生成。
一次完整的 AI API 调用过程就像在高级餐厅点餐的完整体验,让我们用生动的餐厅比喻来详细说明:
阶段一:你的应用程序发出订单(构建请求)
你在应用里输入问题(比如:"写一首关于秋天的诗")并点击发送。
你的应用程序(服务员)立刻开始工作,它会根据预设的规则,将你的问题和其他参数打包成一个标准的 "订单"——也就是一个 HTTP 请求。
这个订单包含几个关键部分:
- 餐厅地址:API 的端点
- 使用 SDK 时:只需要设置 base_url 为
https://api.deepseek.com,SDK 会自动处理完整路径 - 直接 HTTP 请求时:需要完整 URL,如
https://api.deepseek.com/v1/xxx
- 使用 SDK 时:只需要设置 base_url 为
- 会员卡:你的 API 密钥,放在请求头里,证明你是合法顾客
- 菜单和口味要求:请求体,通常是 JSON 格式,包含:
model:你要哪位 "主厨" 来做(比如deepseek-chat)messages:你的具体问题和对话历史temperature:你希望主厨的创造力有多高(0 是严格按菜谱,1 是自由发挥)max_tokens:这道菜最多上多少 "道"(控制回答长度)
技术实现示例:
import os
from openai import OpenAI
# 建立连接(找到餐厅)
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'), # 你的会员卡
base_url="https://api.deepseek.com" # 餐厅地址
)
# 下订单
response = client.chat.completions.create(
model="deepseek-chat", # 选择主厨
messages=[ # 点单内容
{"role": "system", "content": "你是一个专业的AI助手"},
{"role": "user", "content": "请介绍一下AI API调用的基本流程"}
],
temperature=0.7, # 口味要求
max_tokens=1000, # 份量控制
stream=False # 上菜方式
)阶段二:订单穿越网络(网络传输)
这个 HTTP 请求通过互联网发送出去。它会经过你的路由器、运营商网络、各种骨干网路由器,最终抵达 AI 服务商的服务器。这个过程就像服务员把你的订单从餐桌送到厨房入口,中间可能要穿过餐厅的大堂。为了保证订单不被偷看或篡改,整个过程通常是加密的(HTTPS)。
阶段三:AI 服务商内部处理(餐厅后厨的复杂运作)
这是最核心、最复杂的部分。你的请求进入 AI 服务商的庞大系统后,会经历以下步骤:
(1)抵达大门:API 网关(前台兼保安)
- 身份验证:检查你的 API 密钥是否有效、是否有余额
- 速率限制:防止你点单太快把厨房搞乱(比如每分钟最多 60 次)
- 请求校验:检查你的订单格式对不对(JSON 是否合法)
(2)进入等待区:任务队列(取餐号系统)
- AI 推理非常消耗计算资源,如果所有订单都直接冲向主厨,系统会崩溃
- 请求会被放入任务队列里排队,保证公平有序
- 实现削峰填谷,即使瞬间来一万单,厨房也能按自己的节奏处理
(3)预处理:备菜
- 内容审查:检查你的提问是否包含违规内容
- 提示词优化:在你的要求上加点 "标准配方",引导主厨做出更符合预期的菜
- 参数解析:提取 temperature、max_tokens 等参数,准备好交给主厨
(4)核心烹饪:模型推理(明星主厨上场)
- 分词:主厨不认识汉字,只认识数字。他会先把你的问题切分成一个个最小的单元(Token),然后转换成数字 ID
- 模型计算:这些数字 ID 被送入加载在 GPU 上的神经网络模型中,模型会一层一层地处理这些输入
- 自回归生成:模型预测出第一个词,然后把这个词也作为输入,再去预测第二个词,如此循环直到生成完整回答
(5)出锅装盘:后处理与响应
- 格式化:将结果打包成标准的 JSON 格式
- 最终审查:再次快速检查生成的内容是否安全合规
- 计费信息:记录这次请求消耗了多少 Token
阶段四:返回你的餐桌(响应返回)
封装好的 HTTP 响应原路返回,穿过 API 网关,经过互联网,最终回到你的应用程序。这里有一个关键点:流式传输 vs 非流式传输
- 非流式:等主厨把整道菜都做完,一次性端上桌
- 流式:主厨每炒好一小部分,服务员就立刻端给你(打字机效果)
应用呈现:
# 接收菜品并享用
if response.choices:
ai_response = response.choices[0].message.content # 提取主厨的杰作
print(ai_response)
else:
print("抱歉,今天主厨休息了")响应数据结构:
{
"id": "chatcmpl-123",
"model": "deepseek-chat",
"choices": [
{
"message": {
"role": "assistant",
"content": "AI API调用的基本流程包括..."
}
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}完整流程示意图:
[你] <-> [你的App] <--(HTTP请求)--> [互联网] <--(HTTPS)--> [AI服务商]
|
V
[API网关] (鉴权, 限流)
|
V
[任务队列] (排队)
|
V
[预处理服务] (审查, 解析参数)
|
V
[模型推理服务] (在GPU上进行分词、计算、生成)
|
V
[后处理服务] (格式化JSON, 计费)
|
V
[API网关]
|
V
[互联网]
|
V
[你] <--(显示结果)--> [你的App] <--(HTTP响应)--> [互联网] <--(HTTPS)--> [AI服务商]三、前面的三个核心问题
1. 参数
1.1 简介
参数是控制 AI 行为的关键,通过参数的调整,我们可以精确控制 ai 的输出风格,创造力和内容长度等。参数的本质其实影响的是模型的“决策”。AI 生成文本的本质是预测下一个最可能的词,ai 本身是不理解这些词的含义的,而参数最终影响的就是”可能性“的计算方式。
Temperature:控制创造性和确定性的平衡。例如 Temperature 越低,倾向于选择最高概率的词,输出更加的稳定、严谨、也更加的可预测,适合事实性的会打、技术文档等。而 Temperature 越高,就会给低概率的词更多出现的机会,输出就更加的随机、多样、更加有“创意”,适合创意写作,头脑风暴(万一就组合出了一个创造性的东西呢?万一呢,是吧)。
Max Tokens:限制 AI 回复的最大长度。控制 API 调用成本,避免资源浪费。
Top P:控制词汇选择的多样性。
1.2 如何管理
如何优雅的管理参数?可以将参数配置分离出来, 使业务逻辑更加清晰和可维护,例如:
# 好的设计
PRESETS ={
"creative writer": {
"temperature": 0.9,
"max tokens": 1000
},
"fact checker": {
"temperature": 0.1,
"max tokens": 500
}
}
# 调用时
params =PRESETS ["creative_writer"]
# API 调用
response =openai.ChatCompletion.create (
model = "gpt-3.5-turbo",
messages = messages,
**params
)2. 对话
2.1 短期记忆?
AI模型本身是无状态的,每次请求都是独立的。那么如何能让AI拥有短期的记忆?如何当AI记住我们是谁?怎么记住历史,实现跟我们的连贯对话?
解决方案就是欺骗AI,让它自己以为自己记得,核心思想就是:把历史对话(前面输入的内容,AI输出的内容等)作为新的请求的一部分,一起发送给AI。
2.2 对话格式示例
例如,我们可以设计一个对话记录的列表Messages,它可以包含“角色——内容”,里面的角色包括:
System:代表系统级别的指令,通常用来放置提示词,提示词可以进一步控制(或者说覆盖)大模型的行为,可以用于设定AI的全行为和背景,具有高优先级,但同时也存在不稳定的风险
User:用户发出的指令。
Assistant:LLM 给出的回复。
【例】
messages = [
{
"role": "system",
"content":"你是一个有帮助的助手。"
},
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content":"你好!有什么可以帮你的吗?"
},
{
"role": "user",
"content": "我刚才想说什么? "
}
]大模型所谓的“记忆”对话,其实是依赖于这个列表来传递信息的。
2.3 对话管理
对话管理就是AI的短期记忆,通过上下文的注入,让AI能够记住对话历史实现连贯的多轮交流。本质上就是一个不断追加和更新历史列表的过程。
一个问题:随着上下文越来越多,我们发给ai的内容越来越多,AI处理的成本也越来越高,所以很多大模型都设置了上下文长度,例如64KB、128KB,千问可以达到256KB。
3. 状态
3.1 状态管理
前面提到如何让AI"认识我们",记住用户的姓名、偏好等个人信息,提供个性化服务?这就需要状态管理来实现。状态管理是AI的长期记忆系统,通过持久化的存储机制,让AI能够记住用户的信息、偏好和历史交互,从而实现真正的个性化体验。
状态管理可以将底层无状态的AI服务,包装成用户感知上有状态体验的关键技术。
状态管理的核心价值:
- 个性化服务:记住用户偏好,提供定制化体验
- 上下文感知:了解用户习惯和历史交互
- 持续学习:基于历史数据优化服务质量
- 用户体验:减少重复信息输入,提高交互效率
3.2 长期记忆和短期记忆的对比
| 特性 | 上下文(短期记忆) | 状态(长期记忆) |
|---|---|---|
| 生命周期 | 会话级(临时) | 应用级(持久) |
| 内容 | 完整的对话历史 | 关键信息片段(如用户名、偏好) |
| 存储 | 内存中的列表 | 变量、文件、数据库、缓存 |
| 作用 | 维持对话连贯性 | 提供个性化体验 |
| 更新频率 | 每次对话更新 | 按需更新 |
| 数据量 | 相对较大 | 相对较小 |
实际应用示例:
- 短期记忆:记住当前对话中提到的"我刚才说的那个项目"
- 长期记忆:记住用户"喜欢喝咖啡"、"住在北京"等个人信息
3.3 状态存储的载体选择
状态存储的选择取决于应用的规模、性能和持久性需求:
1. 内存变量(临时存储)
# 简单但易失性存储
user_preferences = {
"user_id": "12345",
"preferred_language": "中文",
"theme": "dark",
"last_login": "2024-01-15"
}- 优点:访问速度快,实现简单
- 缺点:程序重启后数据丢失
- 适用场景:开发测试、临时会话
2. 文件存储(轻量级持久化)
# JSON文件存储示例
import json
# 保存状态
def save_user_state(user_id, state):
with open(f'user_{user_id}.json', 'w', encoding='utf-8') as f:
json.dump(state, f, ensure_ascii=False, indent=2)
# 读取状态
def load_user_state(user_id):
try:
with open(f'user_{user_id}.json', 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
return {}- 优点:实现简单,数据持久化
- 缺点:并发访问可能有问题,性能有限
- 适用场景:单用户应用、小型项目
3. 数据库存储(企业级方案)
- SQLite:轻量级,适合桌面应用
- Redis:高性能内存数据库,适合缓存
- MySQL/PostgreSQL:关系型数据库,适合复杂查询
- MongoDB:文档数据库,适合灵活的数据结构
3.4 状态信息的提取策略
从用户输入中提取关键信息是状态管理的核心环节:
(1)基于规则的方法:关键词匹配、正则表达式匹配
def extract_user_info(text):
info = {}
# 关键词匹配
if "我叫" in text:
# 提取姓名:"我叫张三" -> "张三"
name = text.split("我叫")[1].split(" ")[0]
info["name"] = name
# 正则表达式匹配
import re
email_match = re.search(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', text)
if email_match:
info["email"] = email_match.group()
return info(2)基于AI的智能提取:例如自然语言处理(Natural Language Processing,简称 NLP)技术等。
参考资料: