Essay
大模型应用基础篇:和大模型搭上话
只要会写python就可以学。 ——我自己说的 前言 提示工程、上下文工程、skills、MCP、tooluse、RAG、思维链、Agent。 AI应用的花园,开出如此多的花朵。 火爆的OpenClaw,其本质也是由一个一个API调用请求驱动的Agent。 一切的一切,都从一个API调用请求开始。 我将从API的调用
只要会写python就可以学。 ——我自己说的
前言
提示工程、上下文工程、skills、MCP、tool_use、RAG、思维链、Agent。
AI应用的花园,开出如此多的花朵。 火爆的OpenClaw,其本质也是由一个一个API调用请求驱动的Agent。 一切的一切,都从一个API调用请求开始。
我将从API的调用讲起,逐步搭建一个Agent。
参考标准:OpenAI的api文档
本教程所述内容,为支持最广泛的Completion API,而非response API。前者是无状态协议,服务器不托管状态,由客户端维护对话历史,后者由OpenAI的服务器维护状态。
几乎所有的大模型(DeepSeek、Kimi、ZAI等)都支持Completion API,response API少数平台跟进,故本教程所述内容,主要针对Completion API。
DeepSeek 的API量大管饱,容易获取,因此本次教程采用DeepSeek的官方API。
基础篇:和AI搭上话
1. HTTP请求-扒开AI框架的外衣
API是各厂商包装的大模型接口,本质上是一个HTTP请求。无论是curl、Python还是nodejs,都是在模拟浏览器发起请求,并且获得回复。
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-d '{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
"stream": false
}'
上述指令是DeepSeek官方示例,用于发起一个简单的对话请求。其中:
-H表示http请求,Content-Type是第一个参数,表示请求是json格式。第二行是认证头,认证密钥。-d是请求内容。是一个json格式的参数体,其中包含了model、messages等参数。 要完成一个基础的调用,本质上只需要关键的baseURL以及填入API密钥。
例如,下面是一个Python的调用示例:
from openai import OpenAI
client = OpenAI(
api_key="sk-xxxxxx",
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"},
]
)
print(response.choices[0].message.content)
输出:
第九行代码print(response.choices[0].message.content)返回一个返回一个json格式体,格式如下:
response
├── id
├── object
├── created
├── model
├── choices ← 关键
└── usage
其中choices是一个列表:
choices: [
{
index: 0,
message: {
role: "assistant",
content: "Hello! How can I help you?"
},
finish_reason: "stop"
}
]
其中的message即模型的返回内容。choices[0].message.content来获取返回的内容。
此外,chat.completion返回结构(上文的response)的其余五个字段的功能简要介绍如下:
- id:本次请求的唯一标识符,用于日志追踪、问题排查和计费核对。
- object:返回对象的类型标记,用于区分响应数据结构类别。
- created:响应生成的 Unix 时间戳,用于记录请求发生时间。
- model:实际执行推理的模型名称,用于确认调用的模型版本。
- usage:本次请求的 token 统计信息,用于成本计算与性能分析。 在构建应用时,可以通过这些字段来记录统计以及验证、问题排查。
2. messages-交互的主舞台
messages是用户与大模型交互的主要参数,所有的历史消息,都会在messages参数中填充。一个标准的messages体如下所示:
messages: [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hello! How can I help you?"}
]
message是一个列表,每个元素都是一个字典,字典包含了role和content两个键值对。
- role:角色,表示消息的发送者,常用的是system、user或assistant。
- content:消息的具体内容,是一个字符串。
"role"参数中,最常用的有三类:system、assistant 和 user。从字面不难理解,user 代表用户端发送的消息。system 与 assistant 的区别在于:
- system 是系统“默认”的角色设定,通常从对话开始便一直存在,可用于定义模型的角色、语言风格等基础设定。
- assistant 则对应模型自身的回复内容,例如在网页对话中由 GPT 生成的回答就是 assistant 角色的内容。
需注意一点:在网页应用中,系统提示词(system)通常会始终保留在上下文中,不会因长度限制而被截断;而 assistant 和 user 的内容则可能因上下文过长被部分移除。
然而,通过 API 调用大模型时,模型本身不具备记忆能力,因此需要在每一轮请求的 messages 数组中重新添加 system 消息,否则模型将无法维持之前的设定。
对于一般的对话场景,使用以上三种角色已足够。
除此之外,较常用的还有 tools 角色,它们是构建智能体(Agent)功能的关键组成部分,我们将在后续内容中进一步探讨。
3. stream流式输出-让AI“边想边说”
在前面的请求中,已经调用过API,与模型进行交互。但是在运行代码的时候,由于模型生成内容需要时间,我们只能等待其输出。
从发起到得到回答,一共消耗1.13秒。

如果模型生成内容很长,这个时间会很久,超过10秒。例如更改prompt为“生成一个600字的小说。”
如果要我们等内容完全生成再返回,这个等待时间会非常影响体验(花了44秒多)。

为了解决这个问题,我们可以采用流式输出(streaming)的方式。流式输出是指模型在生成内容的过程中,将内容逐步返回给客户端,而不是等待所有内容生成完毕后一次性返回。
其请求略有不同:
from openai import OpenAI
client = OpenAI(
api_key="sk-xxxxxx",
base_url="https://api.deepseek.com",
)
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream = True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
stream更改为True开启流式输出,并且获取这个流式输出的内容的方式,也与之前不同。
在流式输出中,模型会逐步返回内容,每次返回一个“chunk”,我们可以通过迭代这个流来获取每个chunk的内容。 每个chunk的的结构体如下所示:
chunk = {
"id": "chatcmpl-xxx",
"object": "chat.completion.chunk",
"created": 1700000000,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"delta": {...},
"finish_reason": null
}
]
}
可以看见,其中大部分的对象功能,和非流式对象ChatCompletion一致(id、object、model等),不同的是其中的choices属性,其中choices[0]不再包含messages,而是delta。delta是一个字典,包含了当前chunk的内容,可能是一个或若干个token,也可能是role等值,例如,delta可能是下面的结构体(“输出一半的Hello”):
"delta": {
"content": "Hel"
}
这是一种通过SSE协议增量更新、单向通信的形式,提供更低延迟的传输。
传输过程可以用下面的时序呈现:
Client Server
| |
| POST /chat.completions |
| stream=True |
|------------------------------->|
| |
|<---- data: {role:assistant} --|
|<---- data: {"Hel"} ----------|
|<---- data: {"lo"} -----------|
|<---- data: {finish:stop} ----|
|<---- data: [DONE] -----------|
| |
Connection stays open until DONE
其中,第一个chunk的delta中,role为assistant,内容为空,代表模型开始生成回复。中途,模型会逐步返回内容,每个chunk的delta中,content包含了当前生成的token。最后,当模型生成完成时,会返回一个finish_reason为stop的chunk,代表模型生成结束。
值得注意的是,SSE是一种单向传输协议,并且传输的内容时间上不是完整的json格式体,而是文本流,用两个换行符表示每个chunk的结束。而本地的Python代码中,我们需要手动解析这个文本流,将每个chunk的内容提取出来。OpenAI的库封装了提取逻辑,stream = client.chat.completions.create(...)这行代码提取出了每个chunk。
以下是流式输出的示例,展示了模型生成内容的过程:
data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]}
data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"delta":{"content":"Hel"},"index":0}]}
data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"delta":{"content":"lo"},"index":0}]}
data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop","index":0}]}
data: [DONE]
其中,[DONE]是一个特殊的chunk,代表模型生成结束。
4. 思考模式-偷听大模型在想什么
目前很多模型都带有思考模式,例如deepseek-chat、MiniMax-M2.5等。
但由于安全性,还有商业化等原因,OpenAI 的 ChatCompletion API 中并没有提供直接的 thinking 模式的接口,而 Deepseek 和小米等一些开源的厂商使用额外“extra_body”来调整thinking参数,获取模型的思考过程,例如:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "生成一个七言律诗,赞美中国"},
],
stream=True,
extra_body={
"thinking": {"type": "enabled"}
}
)
而有一些厂商,例如智谱(ZAI),他们在 API 的后端也提供了 thinking 的转化,可以直接使用thinking来调整思考模式的启用与否。
总之,对于支持思考的模型,通过thinking标签或者extra_body标签的thinking标签,可以进行思考模式的启用。
思考模式的返回内容,由reasoning_content承接,在流式chunk中,替代content字段。
Client Server
| |
| POST /chat/completions |
| stream=True |
|-------------------------------------->|
| |
|<---- data: {role: assistant} ---------|
| |
|<---- data: {reasoning: "Let me "} ----|
|<---- data: {reasoning: "check the "} -|
|<---- data: {reasoning: "weather..."} -|
| |
|<---- data: {content: "The "} ---------|
|<---- data: {content: "weather "} -----|
|<---- data: {content: "is sunny."} ----|
| |
|<---- data: {finish_reason: stop} -----|
|<---- data: [DONE] --------------------|
| |
Connection stays open until DONE
其中,前半段的reasoning_content是模型的思考内容,后半段的content是模型的回复内容。因此在流式输出中,我们可以通过判断chunk.choices[0].delta.reasoning_content是否存在,来获取模型的思考内容。
5. 结构化输出-让大模型“站军姿”
LLM的输出,是一个个的token,本质上是文本流,是一种非结构化的数据。
而非结构化的数据难以利用,我们可以通过一些措施来生成结构化的输出。
最简单的就是使用系统提示词约束,要求其依照特定的模板进行填空,得到结构化的json文本。
例如这样一个系统提示词:
用json格式来提取发送文本中的人物、地点、时间,直接输出json,不要输出其余内容。
参考:
{
'name': 小张,
'place': 教室,
'time': 中午12点
}
此时发送一段文本就可以获得json结构的输出(图中是直接在chatgpt网页进行对话。):

如果批量调用API,那么大模型就可以成为一个文本处理组件,进行自动化的批量文本处理。
然而,由于众所周知的“幻觉”问题,大模型有时并不会按照约束进行输出,此时就需要用“失败重试”等机制来进行错误校验。为了加强大模型的约束,减少开发者的开发负担,一些厂商推出了结构化输出(大多是JSON格式输出)。
例如DeepSeek的文档中,在发送的请求中,需要设置response_format 参数的值为{'type': 'json_object'} 来开启结构化输出。但是,在文档中,依然提到“在使用 JSON Output 功能时,API 有概率会返回空的 content。我们正在积极优化该问题,您可以尝试修改 prompt 以缓解此类问题。”
因此,还是需要开发者来进行测试(🤣),同时,也并不是所有的模型都支持response_format 参数。 在使用response_format 参数时,依然需要提供对应的json模板。
import json
from openai import OpenAI
client = OpenAI(
api_key="sk-a9c7cee0e60947708xxxxx",
base_url="https://api.deepseek.com",
)
system_prompt = """
用户会提供一个纯文本,你需要从文本中提取出内容,并以 JSON 格式输出。
示例输入:
小张12点在教室睡觉。
EXAMPLE JSON OUTPUT:
{
"name": "小张",
"place": "教室",
"time": "12点",
"event": "睡觉"
}
"""
user_prompt = "小红元旦那天在教室跳热舞。"
messages = [{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}]
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
response_format={
'type': 'json_object'
},
stream=True
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
输出结果如下所示:
