想要在应用中集成 AI 吗?一篇文章帮你急速入门
LLM 的内部机制虽然极其复杂,但对于普通开发者而言,只需学会如何调用即可。
Hello World
调用过程非常简单,只是一个普通的 HTTP 接口。以 Deepseek 为例:
fetch("https://api.deepseek.com/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: "Bearer <DeepSeek API Key>",
},
body: JSON.stringify({
model: "deepseek-chat",
messages: [{ role: "user", content: "你好!" }],
}),
})
.then((res) => res.json())
.then((completion) => {
console.log(completion.choices[0].message.content);
});
这里有几个需要解释的地方:
- model 指定模型,具体需要根据你使用的模型提供商来决定(例如 Deepseek 提供 chat 和 reasoner 两种模型)
- messages 数组承载对话上下文,其中 role 标识消息来源(这里是 user,代表用户),content 则是此次发言的内容
- 响应结果中的 choices 数组包含生成结果,常规对话场景只需读取第一个元素内容
由于 Deepseek 对 OpenAI 的接口进行了兼容,所以我们也可以使用 OpenAI SDK。它在 HTTP 调用之上进行了一定的包装,使得调用起来更为方便。
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: "https://api.deepseek.com",
apiKey: "<DeepSeek API Key>",
});
openai.chat.completions
.create({
model: "deepseek-chat",
messages: [{ role: "user", content: "你好!" }],
})
.then((completion) => {
console.log(completion.choices[0].message.content);
});
多轮对话
LLM 本身不具备“记忆”能力,每次请求都是独立的。通过以下实验可验证:
let completion = await openai.chat.completions.create({
model: "deepseek-chat",
messages: [{ role: "user", content: "你好!我叫张三,请记住我的名字。" }],
});
console.log(completion.choices[0].message.content);
completion = await openai.chat.completions.create({
model: "deepseek-chat",
messages: [{ role: "user", content: "你还记得我的名字吧?" }],
});
console.log(completion.choices[0].message.content);
可以看到,尽管第一次请求的时候我们已经告知了名字,第二次回答时模型还是“忘记”了。
可通常网页端的大模型都具备记忆的能力,这是怎么一回事呢?
这其中的关键就是 messages,只有在每次请求时携带上完整的消息列表,模型才能知晓我们过去的对话内容。 所以得这样做:
const messages = [
{ role: "user", content: "你好!我叫张三,请记住我的名字。" },
];
let completion = await openai.chat.completions.create({
model: "deepseek-chat",
messages,
});
messages.push(completion.choices[0].message);
messages.push({ role: "user", content: "你还记得我的名字吧?" });
completion = await openai.chat.completions.create({
model: "deepseek-chat",
messages,
});
messages.push(completion.choices[0].message);
console.log(messages);
上下文长度
之前说了,想要模型具备记忆能力,需要传递完整的消息列表。 但模型的处理能力是有限的,超过限制模型将无法处理,而这个最大处理长度就被称之为上下文长度。
也就是说,随着 message 列表长度的增加,可用的上下文将不断减少,直至溢出。
为了解决这个问题,最常见的做法就是截断——只保留最新的消息,从而避免上下文溢出。
而这也就导致了在长时间对话后,模型会出现"遗忘"现象。也解释了为何 AI 代码编辑器(如 Cursor)不直接读取整个项目的源码,因为项目代码量往往远超上下文长度。
实际开发中,如何优雅处理上下文长度溢出的问题,需要开发者针对具体应用场景进行设计。
消息角色
在于大模型的对话过程中,除了用户和模型的对话外,往往还会有其他角色和模型进行对话。
例如,我们可能希望在模型与用户对话开始前,对模型进行一定的预设,比如“你是一个情感分析大师,你需要根据用户的情况给出专业的情感分析”。
又或者,我们会在用户与模型的对话过程中,传递一些中间信息,例如某些应用状态信息。
这时候就体现了角色Role这个概念,目前来说有以下角色:
1. system(系统角色)
- 作用:
设定对话的背景、规则或模型的行为模式。通常用于初始化对话,指导模型如何回应用户。 - 示例:
{ "role": "system", "content": "你是一个幽默的助手,回答时尽量用笑话。" }
2. user(用户角色)
- 作用:
表示用户输入的问题或请求,触发模型的回复。 - 示例:
{ "role": "user", "content": "如何做一杯好喝的咖啡?" }
3. assistant(助手角色)
- 作用:
表示模型生成的回复。在对话历史中,可用于提供上下文或让模型自我修正。 - 示例:
{ "role": "assistant", "content": "先磨咖啡豆,水温控制在90°C左右..." }
4. tool(工具角色)
- 作用(如支持工具调用):
当模型需要调用外部工具(如查询天气、执行计算)时,通过tool角色传递执行结果,供模型继续生成回复。 - 示例:
{ "role": "tool", "name": "get_weather", "content": "{\"temperature\": 22}" }
典型对话结构示例
[
{ "role": "system", "content": "你是一个咖啡专家。" },
{ "role": "user", "content": "如何手冲咖啡?" },
{ "role": "assistant", "content": "需要滤纸、咖啡粉和热水..." },
{ "role": "user", "content": "水温多少合适?" },
{ "role": "assistant", "content": "建议88-92°C。" }
]
Tool Calls
如果仅用大模型处理基础对话场景,显然无法充分发挥其潜力。实际上,模型具备强大的逻辑推理能力,完全能够执行复杂任务(如酒店预订、机票查询等)。但模型本身无法直接操作外部系统,如何解决这个问题?答案是Tool Calls。
通过 Tool Calls,开发者可以为模型注册外部工具。例如提供get_weather天气查询工具后,模型即可在对话过程中智能调用该工具,从而获取实时天气数据。
代码如下:
const messages = [{ role: "user", content: "今天北京天气如何?" }];
let completion = await openai.chat.completions.create({
messages,
model: "deepseek-chat",
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "获取当日的天气信息",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名",
},
},
required: ["city"],
},
},
},
],
});
console.log(completion.choices[0]);
我们通过一个 JSON Schema 描述了一个工具的名称、作用和参数,模型通过这些描述来理解这个函数的功能,并在需要的时候调用这个函数。
那么,什么时候工具才会被调用呢?
答案是模型自己决定,这一点与编程是十分不同的,我们对模型的控制力并不像代码一样强,开发者只能尽全力引导它,但最终如何做、做成什么样,我们很难干预。它会根据当前的任务,决定使用手头的哪些工具,你可以指导他、命令他,但是它也可能无法完成你的预期。
如果你运行上述代码,会发现模型返回了一个特别的结果:
[
{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"index": 0,
"id": "call_0_ea31bd83-1644-4a1e-9e3c-edc3a9565cea",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
]
模型只是返回了一个调用请求,而具体工具在哪,通过何种方式调用,都需要我们自己去实现。 所以新的代码如下:
function chat(messages) {
return openai.chat.completions.create({
messages,
model: "deepseek-chat",
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "获取当日的天气信息",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名",
},
},
required: ["city"],
},
},
},
],
});
}
function getWeather(city) {
// 实际应调用天气API,此处模拟返回
return `${city}晴,25℃`;
}
const messages = [{ role: "user", content: "今天北京天气如何?" }];
let completion = await chat(messages);
let message = completion.choices[0].message;
messages.push(message);
const toolCall = message.tool_calls?.[0];
if (toolCall?.function.name !== "get_weather") throw new Error("未调用工具");
// 省略对调用参数的验证逻辑
const city = JSON.parse(toolCall.function.arguments).city;
const toolResult = getWeather(city);
messages.push({
role: "tool", // tool角色,代表工具调用的结果
content: toolResult,
tool_call_id: toolCall.id,
});
completion = await chat(messages);
messages.push(completion.choices[0].message);
console.log(messages);
注意,并不是所有的模型都支持 Tool Calls,例如 Deepseek R1 就不支持(但仍然可以通过传递提示词来模拟这一行为)。
结构化输出
通过上述 Tool Calls 示例可以看出,模型的核心能力之一在于结构化输出。
这意味着,除了生成自然语言文本外,模型还能根据需求输出 JSON、XML 等标准化格式的数据。
尽管任何模型均可通过提示词引导生成结构化数据,但经过专门训练的模型(如 Deepseek)能更精准地满足格式要求。 例如,在 Deepseek 的 API 调用中,通过设置参数 response_format: { "type": "json_object" },可强制模型输出严格符合 JSON 格式的响应。
这一能力的关键价值在于:将自然语言转化为结构化数据,从而被系统直接解析和应用。
例如:
// 强制JSON输出示例
const completion = await openai.chat.completions.create({
model: "deepseek-chat",
messages: [
{
role: "system",
content: `你是一个结构化数据提取助手。请将用户输入的信息转换为JSON对象,包含以下字段:
- name(中文姓名)
- phone(电话号码)
- email(邮箱地址)
仅输出JSON,不添加额外内容。`,
},
{
role: "user",
content: "帮我记录客户王女士,电话 021-12345678,邮箱wang@company.cn",
},
],
response_format: { type: "json_object" },
});
模型输出:
{
"name": "王女士",
"phone": "021-12345678",
"email": "wang@company.cn"
}