Skip to content

从零到进阶做 Agent(二)大模型调用基础

🕒 Published at:

上一篇我们跑通了第一次模型调用。这一篇把这次调用彻底拆开:messages 到底是什么、怎么做流式输出(打字机效果)、怎么让模型记住多轮对话。还是老规矩,页面顶部可以切换 Python / Node.js。

messages:对话的最小单位

大模型的聊天接口不是「发一句、收一句」那么简单,它接收的是一个消息数组 messages,数组里每条消息都有一个 role(角色):

  • system:系统设定,给模型定人设、定规则,通常放在最前面,只写一次。
  • user:用户说的话。
  • assistant:模型之前的回复。多轮对话时,我们要把它历史回复也放回数组里,模型才「记得」。

关键点:接口本身是无状态的。模型不会自己记得上一轮,是我们每次把完整的对话历史重新发过去,它才能接上下文。理解这一点,后面的「记忆」就水到渠成了。

一、最基础的一次性调用

先封装一个最简单的调用,把复用的 client 单独拎出来。

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

def ask(question: str) -> str:
    resp = client.chat.completions.create(
        model="qwen-plus",
        messages=[
            {"role": "system", "content": "你是一个简洁专业的助手。"},
            {"role": "user", "content": question},
        ],
    )
    return resp.choices[0].message.content

print(ask("Python 里列表和元组的区别是什么?"))
javascript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
});

async function ask(question) {
  const resp = await client.chat.completions.create({
    model: "qwen-plus",
    messages: [
      { role: "system", content: "你是一个简洁专业的助手。" },
      { role: "user", content: question },
    ],
  });
  return resp.choices[0].message.content;
}

console.log(await ask("JavaScript 里 == 和 === 的区别是什么?"));

temperature 是常用的可选参数:0 附近回答更确定、更稳定,适合分类抽取这类任务;调高(如 0.8)更发散,适合创意写作。想控制随机性时在 create 里加上它即可。

二、流式输出:打字机效果

一次性调用要等模型把整段话生成完才返回,长回答会让用户干等。流式输出则是模型每生成一小块(token)就推一块过来,前端立刻显示,体验上就是「一个字一个字往外蹦」。做法是把 stream 打开,然后遍历返回的数据块。

python
def ask_stream(question: str):
    stream = client.chat.completions.create(
        model="qwen-plus",
        messages=[{"role": "user", "content": question}],
        stream=True,
    )
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            print(delta, end="", flush=True)   # 实时打印,不换行
    print()

ask_stream("用三句话介绍一下杭州。")
javascript
async function askStream(question) {
  const stream = await client.chat.completions.create({
    model: "qwen-plus",
    messages: [{ role: "user", content: question }],
    stream: true,
  });
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);   // 实时输出,不换行
  }
  process.stdout.write("\n");
}

await askStream("用三句话介绍一下杭州。");

注意流式返回的每块里,内容在 delta.content 而不是 message.content,而且有些块(比如最后的结束块)内容为空,要判空后再拼接。后面做 Web 界面时,我们会把这个流经过 SSE 推给浏览器,实现网页上的打字机效果。

三、多轮对话:让模型「记住」

前面说过接口无状态,那要实现连续对话,就得自己维护一个 messages 列表:每轮把用户的话追加进去、调用模型、再把模型的回复也追加进去。下一轮把整个列表重新发过去,上下文就接上了。

python
history = [{"role": "system", "content": "你是一个耐心的编程老师。"}]

def chat(user_input: str) -> str:
    history.append({"role": "user", "content": user_input})
    resp = client.chat.completions.create(model="qwen-plus", messages=history)
    answer = resp.choices[0].message.content
    history.append({"role": "assistant", "content": answer})   # 把回复也存回历史
    return answer

print(chat("我想学装饰器,先给我一句话概括。"))
print(chat("那它和闭包有什么关系?"))   # 这里的「它」指装饰器,模型能接上
javascript
const history = [{ role: "system", content: "你是一个耐心的编程老师。" }];

async function chat(userInput) {
  history.push({ role: "user", content: userInput });
  const resp = await client.chat.completions.create({
    model: "qwen-plus",
    messages: history,
  });
  const answer = resp.choices[0].message.content;
  history.push({ role: "assistant", content: answer });   // 把回复也存回历史
  return answer;
}

console.log(await chat("我想学闭包,先给我一句话概括。"));
console.log(await chat("那它和作用域有什么关系?"));   // 「它」指闭包,模型能接上

跑第二句时你会发现,模型准确理解了「它」指代前文,这就是多轮记忆生效了。

一个要注意的坑:历史会越滚越长,token 消耗和费用也随之上涨,太长还会超出模型上下文窗口。生产里通常会做「滑动窗口」(只保留最近 N 轮)或「摘要压缩」(把老对话总结成一小段)。这些「记忆管理」的话题,等我们进入 LangChain 篇会用更优雅的方式处理。

小结与预告

这一篇你掌握了大模型调用的三件套:一次性调用流式输出多轮对话,并且理解了最核心的一点——接口无状态,记忆靠我们自己维护 messages

到这里你已经能用裸 SDK 写出一个像样的命令行聊天机器人了。但随着功能变复杂(模板、解析、检索、记忆、工具……),纯手写会越来越乱。下一篇《提示词工程》,我们先把「怎么把话对模型说清楚」这件事做扎实——包括结构化输出、让模型稳定吐 JSON,以及金融文本分类/抽取的实战案例。