AI 大模型agent开发入门(一)- 理解API接口
本文是agent开发系列的第一篇,适合刚开始接触大模型开发的同学阅读,既然是agent开发,那就离不开大模型调用,学习大模型调用我认为第一课理解API。
很多人第一次学习大模型API,通常是从复制一段代码开始的:
from openai import OpenAI
client = OpenAI(
api_key="your-api-key"
)
response = client.chat.completions.create(
model="your-model",
messages=[
{
"role": "user",
"content": "请介绍一下你自己"
}
]
)
print(response.choices[0].message.content)
代码运行后,终端成功打印出模型回答,于是我们似乎已经“学会了调用大模型”。
开发的过程中,我们可能会就会产生一些疑问:
- API Key 到底是什么,为什么不能直接写进代码?
base_url有什么作用?- 为什么请求参数不是一个
prompt,而是一个messages数组?- 为什么连续对话时,要把之前的聊天记录重新发送?
- Token 为什么会影响费用和请求长度?
- 为什么 ChatGPT 可以逐字显示答案,而自己的程序要等待很久才返回?
这些问题表面上互不相关,实际上都发生在同一次 API 请求中。
要真正理解大模型 API,不需要先背大量参数。更好的方式,是沿着一条请求从程序出发,观察它如何到达模型服务器,又如何把答案返回给用户。
一次最基础的大模型调用,可以概括为:
理解这条链路后,后续学习参数控制、结构化输出、工具调用和 Agent,都会容易很多。
一、程序是怎样找到并访问模型的
调用模型之前,程序必须先解决两个问题:
我是谁?我要访问哪个服务器?
这会用到两个概念,API Key 和 Base URL
client = OpenAI(
api_key=os.getenv("MODEL_API_KEY"),
base_url="https://example.com/v1"
)
API Key 用来证明调用者身份,Base URL 用来确定请求发送到哪里。
1、API Key:程序访问模型服务的身份凭证
大模型 API 通常不是匿名服务。
模型平台收到请求后,需要判断:
- 请求属于哪个账号;
- 账号能否使用当前模型;
- 是否还有可用余额;
- 是否超过调用频率限制;
- 本次请求应该记录到谁的账单中。
API Key 就是程序调用模型服务时使用的身份凭证。
from openai import OpenAI
client = OpenAI(
api_key="your-api-key"
)
从功能上看,它有些类似账号密码。但与普通密码不同,API Key 主要供程序使用这意味着,一旦 API Key 泄露,其他人就可能冒用你的身份调用模型,消耗额度甚至产生费用。因此,不建议把真实 API Key 直接写进代码:
# 不推荐
client = OpenAI(
api_key="sk-xxxxxxxxxxxxxxxx"
)
#更常见的做法,是通过环境变量读取:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("MODEL_API_KEY")
)
#Windows PowerShell 可以这样设置
$env:MODEL_API_KEY="your-api-key"
#Linux 或 macOS 可以这样设置:
export MODEL_API_KEY="your-api-key"
这样做有三个明显好处:
- 避免密钥随着代码上传到 GitHub;
- 开发、测试和生产环境可以使用不同的 Key;
- 更换密钥时不需要修改业务代码。
.env 文件虽然比直接写进代码更方便,但它仍然包含真实密钥,在项目上传git时也要加入 .gitignore:
.env
2、Base URL:告诉程序模型服务器在哪里
API Key 解决了“我是谁”,Base URL 则解决了“请求发到哪里”。
client = OpenAI(
api_key=os.getenv("MODEL_API_KEY"),
base_url="https://example.com/v1"
)
Base URL 可以理解为一组 API 接口共同使用的根地址。
SDK 会在它后面拼接具体接口路径。例如聊天接口最终可能请求:
https://example.com/v1/chat/completions
很多模型平台都提供所谓的“OpenAI 兼容接口”。
它的意思通常不是这些平台使用了 OpenAI 的模型,而是它们采用了相似的请求格式。开发者可以继续使用 OpenAI SDK,只修改以下内容:
- API Key;
- Base URL;
- 模型名称。
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("MODEL_API_KEY"),
base_url="模型平台提供的地址"
)
response = client.chat.completions.create(
model="模型名称",
messages=[
{
"role": "user",
"content": "你好"
}
]
)
这也是为什么同一套代码稍作修改,就可能接入 Kimi、DeepSeek、Qwen、OpenRouter 或其他兼容平台。
不过,“OpenAI 兼容”并不等于所有能力完全一致。
不同平台可能在以下方面存在差异:
- 支持的模型参数不同;
- 流式返回格式不同;
- 是否支持图片输入;
- 是否支持工具调用;
- 是否支持结构化输出;
- 错误码和限流规则不同。
因此,兼容接口可以降低接入成本,但正式开发时仍要查看对应平台的官方文档。
3、Messages:模型本次能够看到的对话
找到服务器并完成身份认证后,程序需要把问题发送给模型。
现代聊天模型通常不是只接收一个字符串,而是接收一组消息:
messages = [
{
"role": "system",
"content": "你是一名耐心的 Python 教师。"
},
{
"role": "user",
"content": "请解释什么是装饰器。"
}
]
这里的 role 表示消息由谁发出。
最常见的角色包括:
system:定义模型的身份、总体规则和回答方式;user:用户当前提出的问题;assistant:模型之前返回的回答。
例如,一个连续对话可能是:
messages = [
{
"role": "system",
"content": "你是一名 Python 教师,回答时尽量使用简单示例。"
},
{
"role": "user",
"content": "什么是列表推导式?"
},
{
"role": "assistant",
"content": "列表推导式是一种简洁创建列表的语法。"
},
{
"role": "user",
"content": "再给我一个带条件判断的例子。"
}
]
这里有一个非常重要的事实:
大多数聊天 API 本身并不会自动记住你上一轮说了什么。
第二次调用时,之所以模型能理解“再给我一个例子”指的是什么,是因为程序把前面的消息重新发送给了模型。
也就是说,模型的“聊天记忆”通常不是模型平台自动保存的,而是由调用方维护 messages 列表实现的。
你的程序需要负责:
- 保存历史消息;
- 将必要历史重新发送;
- 删除无关历史;
- 在消息过长时进行摘要或截断。
这一点会直接引出下一个核心概念:Token。
二、模型看到的不是文字,而是 Token
1、什么是token
当请求到达模型服务器后,模型不会直接按照汉字数或单词数处理文本。
在真正进入模型之前,文本会先经过 Tokenizer,也就是分词器,被切分成一系列 Token。
Token 是模型处理文本时使用的基本单位。
例如:
请帮我写一个 Python 函数
这句话并不一定按照每个汉字、每个英文单词分别计算。
因此,下面两种说法都不准确:
1 个汉字一定等于 1 个 Token
1 个英文单词一定等于 1 个 Token
Token 之所以重要,是因为它同时决定三件事:
- 请求费用;
- 请求能够容纳的内容长度;
- 模型处理请求所需的时间。
2、输入 Token 不只是当前问题
一次请求中的输入 Token,通常包括:
System 提示词
+ 历史对话
+ 当前用户问题
+ 工具调用结果
+ 检索到的文档
+ 其他附加上下文
假设用户当前只问了一句话:
请继续。
这句话本身可能很短。
但如果程序同时发送了前面几十轮聊天,那么平台计算的不是“请继续”这三个字,而是整份 messages 中的全部内容。
例如:
系统提示词:1,000 Token
历史对话:18,000 Token
当前问题:20 Token
这次请求的输入量大约是 19,020 Token,而不是 20 Token。
这也是为什么聊天时间越长,后续调用往往越贵、越慢。
3、上下文窗口是一张有限大小的工作台
每个模型都有自己的上下文窗口,例如:
32K
128K
256K
1M
这里的 K 通常表示约一千个 Token。
很多初学者会把上下文窗口理解为:
用户一次最多可以输入多少内容。
但更准确的理解是:
模型在一次请求中能够同时处理的全部 Token 数量。
其中不仅包括用户输入,也通常需要包括模型即将生成的内容。
可以把上下文窗口理解为一张固定大小的工作台。
工作台上可能放着:
- 系统提示词;
- 历史对话;
- 当前问题;
- 检索资料;
- 工具执行结果;
- 模型需要填写的答案。
前面的资料放得越多,留给模型生成答案的空间就越少。
假设某个模型的上下文窗口是 128K Token,当前请求中已经包含:
系统提示词:5K
历史对话:80K
当前输入:20K
工具结果:10K
输入部分已经占用了 115K Token。
理论上剩余空间大约为 13K Token。如果程序仍要求模型生成 20K Token,就可能超过上下文限制。
因此,上下文窗口并不等于单纯的“最大输入长度”。
它更接近:
输入 Token + 输出 Token ≤ 上下文窗口
具体计算规则会因模型和接口而异,但这种理解适用于绝大多数开发场景。
4、为什么上下文不是越长越好
上下文窗口越大,意味着模型能够一次读取更多资料,但不代表应该把所有信息都塞进去。
过多上下文可能带来几个问题:
- 请求费用增加;
- 响应速度变慢;
- 无关信息干扰模型判断;
- 重要指令被淹没;
- 更容易触发长度限制。
实际开发中,通常需要主动管理上下文。
常见做法包括:
- 只保留最近几轮对话;
- 将较早内容压缩成摘要;
- 删除和当前问题无关的消息;
- 通过检索只取回相关文档;
- 避免重复发送完全相同的大段提示词;
- 对超长文本进行分块处理。
例如,一个聊天程序不一定要永久保留全部对话:
MAX_HISTORY = 10
recent_messages = messages[-MAX_HISTORY:]
更复杂的做法,是在对话变长后,将旧消息总结为一段较短的摘要:
此前对话摘要:
用户正在开发一个 Unity 项目,已经完成对象池基础实现,
当前希望增加异步资源加载和异常处理。
相比发送几十轮完整消息,摘要可以显著降低 Token 消耗。
不过摘要也会丢失细节,因此不能机械地压缩所有内容。哪些信息必须保留,取决于具体业务。
三、模型生成的答案如何回到程序
1、流式输出
模型开始生成内容后,服务器还需要决定:如何把结果返回给调用方。
最简单的方式,是等模型生成完整答案后,再一次性返回。
response = client.chat.completions.create(
model="your-model",
messages=[
{
"role": "user",
"content": "请介绍 Python 的主要特点"
}
]
)
print(response.choices[0].message.content)
这种模式的流程是:
发送请求
→ 等待模型完成全部生成
→ 返回完整答案
→ 程序开始显示
如果答案很短,等待感并不明显。
但当模型需要生成长文章或大量代码时,用户可能等待十几秒,却看不到任何内容,容易误以为程序卡住了。
ChatGPT、Claude、Kimi 等产品能够逐步显示答案,是因为它们通常采用了 Streaming,也就是流式输出。
开启流式输出后,服务器不会等待完整答案生成完毕,而是将已经生成的内容片段持续发送给程序:
模型生成一部分
→ 立即返回一部分
→ 模型继续生成
→ 继续返回
Python 示例通常类似这样:
stream = client.chat.completions.create(
model="your-model",
messages=[
{
"role": "user",
"content": "请介绍 Python 的主要特点"
}
],
stream=True
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
其中:stream=True表示启用流式返回。程序收到的每个 chunk 都只是完整答案的一部分,因此需要不断读取并拼接。
输出过程可能类似:
Python
Python 是
Python 是一种
Python 是一种高级编程语言……
需要注意,Streaming 的核心价值通常不是大幅缩短模型生成完整答案的总时间,而是缩短用户第一次看到内容的时间。
假设模型完整生成答案需要 15 秒:
非流式模式用户可能等 15 秒后才看到全部内容;流式模式用户可能在第 1 秒就看到第一段文字。
因此,流式输出特别适合:
- AI 聊天页面;
- AI 写作工具;
- 代码生成工具;
- 长文本生成;
- 需要支持“停止生成”的场景。
2、Streaming 也会增加开发复杂度
流式输出看起来只是增加了一个 stream=True,但在真实项目中,还需要处理很多问题:
- 多个文本片段如何拼接;
- 网络中断后如何处理;
- 用户点击停止时如何终止请求;
- 已生成内容是否需要实时保存;
- 工具调用参数如何从多个片段中组合;
- 前端如何避免频繁刷新造成卡顿;
- 流式过程中出现错误如何提示用户。
尤其是结构化输出场景。
假设模型需要返回 JSON:
{
"title": "大模型 API 入门",
"score": 90
}
在流式过程中,你可能先收到:
{
"title": "大模型
此时它还不是合法 JSON,程序不能直接解析。
因此,下面这些业务通常更适合等待完整结果:
- 严格 JSON 输出;
- 数据抽取;
- 后台批处理;
- 自动化任务;
- 需要完整校验后才能继续执行的流程。
Streaming 不是必须开启的高级功能,而是一种结果交付方式。
判断是否使用它,可以问自己一个问题:
用户是否需要在模型生成完成之前,就看到中间内容?
需要,就使用流式输出;不需要,则一次性返回往往更加简单可靠。
四、把一次 API 请求重新串起来
现在回头看最开始的代码:
from openai import OpenAI
client = OpenAI(
api_key="your-api-key"
)
response = client.chat.completions.create(
model="your-model",
messages=[
{
"role": "user",
"content": "请介绍一下你自己"
}
]
)
print(response.choices[0].message.content)
整个API可以概括为:
后续学习的很多能力,都建立在这条请求链路上:
temperature控制生成结果的随机程度;- 输出长度参数限制模型生成规模;
- Structured Output 约束模型返回固定格式;
- Function Calling 允许模型请求外部工具;
- Rate Limit 限制单位时间内的请求和 Token 数;
- Retry 处理限流、超时和服务异常;
- RAG 将检索资料加入模型上下文;
- Agent 在模型、工具和状态之间组织多轮执行。
这些知识并不是相互独立的功能清单,而是不断扩展同一条调用链路。
结语
学习大模型 API 最容易陷入的误区,是一开始就背模型名称、接口参数和 SDK 写法。
这些内容变化很快。
模型会更新,接口会升级,不同厂商的参数也不完全一致。但一次请求背后的核心逻辑相对稳定:
- 程序需要凭证才能访问服务;
- 请求需要发送到正确的接口地址;
- 模型根据本次提供的消息理解上下文;
- 文本会被转换为 Token;
- Token 影响费用、速度和上下文容量;
- 生成结果可以一次性返回,也可以流式返回。
只要理解了这条链路,即使以后更换模型平台、SDK 或编程语言,也只是代码形式发生变化,底层思路并没有改变。
下面这篇文章可以让模型不仅仅是回答问题,还可以让他调用工具
AI 大模型 Agent 开发入门(二):从让AI从“会回答”到“会做事”-CSDN博客
更多推荐





所有评论(0)