想要在应用中集成 AI 吗?一篇文章帮你急速入门

103 阅读7分钟

想要在应用中集成 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);

01-02.png 可以看到,尽管第一次请求的时候我们已经告知了名字,第二次回答时模型还是“忘记”了。

可通常网页端的大模型都具备记忆的能力,这是怎么一回事呢?

这其中的关键就是 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);

01-03.png

上下文长度

之前说了,想要模型具备记忆能力,需要传递完整的消息列表。 但模型的处理能力是有限的,超过限制模型将无法处理,而这个最大处理长度就被称之为上下文长度

也就是说,随着 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);

01-04.png 注意,并不是所有的模型都支持 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"
}