curl 请求调用 vLLM 推理的方法

vLLM 提供了 OpenAI 兼容的 HTTP API,你可以通过 curl 请求调用模型推理。以下是主要的使用方法:

基本推理请求

启动 vLLM 服务器:

vllm serve Qwen/Qwen2.5-1.5B-Instruct

使用 curl 发送推理请求:

curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }'
```[1](#0-0) 

### 聊天对话请求

```bash
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Who won the world series in 2020?"}
        ]
    }'
```[2](#0-1) 

### 使用 LoRA 适配器

如果服务器启用了 LoRA 支持,可以指定特定的适配器:
```bash
curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "sql-lora",
        "prompt": "Generate a SQL query for:",
        "max_tokens": 50,
        "temperature": 0.1
    }'
```[3](#0-2) 

### 在不同部署环境中的使用

**Kubernetes 环境**:
```bash
curl -X POST http://localhost:30080/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "facebook/opt-125m",
    "prompt": "Once upon a time,",
    "max_tokens": 10
  }'
```[4](#0-3) 

**RunPod 云平台**:
```bash
curl https://<pod-id>-8000.proxy.runpod.net/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "<model-name>",
        "messages": [
            {"role": "user", "content": "Hello, how are you?"}
        ],
        "max_tokens": 50
    }'
```[5](#0-4) 

### 其他有用的 API 端点

**查看可用模型**:
```bash
curl http://localhost:8000/v1/models
```[6](#0-5) 

**健康检查**:
```bash
curl http://localhost:8000/health
```[7](#0-6) 

## Notes

- vLLM 默认在 `http://localhost:8000` 启动服务器
- 所有请求都需要设置 `Content-Type: application/json` 头部
- 支持的 API 包括 Completions、Chat Completions、Embeddings 等
- 可以通过 `--api-key` 参数启用 API 密钥验证
- 服务器会自动应用模型仓库中的 `generation_config.json` 配置,除非使用 `--generation-config vllm` 禁用

Wiki pages you might want to explore:
- [Overview (vllm-project/vllm)](/wiki/vllm-project/vllm#1)
- [Build System and Deployment (vllm-project/vllm)](/wiki/vllm-project/vllm#11)
- [LoRA Adapter Management (vllm-project/vllm)](/wiki/vllm-project/vllm#6.4)

### Citations

**File:** docs/getting_started/quickstart.md (L188-190)
```markdown
```bash
curl http://localhost:8000/v1/models

**File:** docs/getting_started/quickstart.md (L199-208)
```markdown
```bash
curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }'

**File:** docs/getting_started/quickstart.md (L239-249)
```markdown
```bash
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Who won the world series in 2020?"}
        ]
    }'

**File:** docs/features/lora.md (L94-103)
```markdown
```bash
curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "sql-lora",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }' | jq

**File:** docs/deployment/integrations/production-stack.md (L80-88)
```markdown
```bash
curl -X POST http://localhost:30080/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "facebook/opt-125m",
    "prompt": "Once upon a time,",
    "max_tokens": 10
  }'

**File:** docs/deployment/frameworks/runpod.md (L52-62)
```markdown
    ```bash
    curl https://<pod-id>-8000.proxy.runpod.net/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{
            "model": "<model-name>",
            "messages": [
                {"role": "user", "content": "Hello, how are you?"}
            ],
            "max_tokens": 50
        }'
    ```

File: docs/deployment/frameworks/runpod.md (L84-87)


```bash
curl https://<pod-id>-8000.proxy.runpod.net/health
## vLLM API 类型区别及底层调用机制

### 主要 API 类型区别

vLLM 提供的 OpenAI 兼容 API 主要有以下类型:

| API 类型 | 用途 | 适用模型 | 端点 |
|---------|------|----------|------|
| **Completions API** | 文本补全生成 | 生成式模型 | `/v1/completions` |
| **Chat Completions API** | 多轮对话生成 | 带聊天模板的指令模型 | `/v1/chat/completions` |
| **Embeddings API** | 文本向量化 | 嵌入模型 | `/v1/embeddings` |
| **Responses API** | 响应管理(支持工具调用) | 生成式模型 | `/v1/responses` | [1](#1-0) 

### 底层调用流程

所有 API 请求都遵循相同的底层调用架构:

```mermaid
sequenceDiagram
    participant Client as "HTTP Client"
    participant API as "FastAPI Server"
    participant Serving as "OpenAIServing*"
    participant Engine as "EngineClient"
    participant Core as "EngineCore"
    participant GPU as "GPU Worker"

    Client->>API: POST /v1/xxx
    API->>Serving: 路由到对应处理器
    Serving->>Serving: 解析请求参数
    Serving->>Engine: generate(request)
    Engine->>Core: 提交请求
    Core->>GPU: 执行推理
    GPU-->>Core: 返回结果
    Core-->>Engine: 流式输出
    Engine-->>Serving: 处理输出
    Serving-->>API: 格式化响应
    API-->>Client: 返回结果

具体实现差异

1. Completions API

  • 直接处理文本提示
  • 通过 OpenAIServingCompletion 处理请求
  • 调用 create_completion() 方法 2

2. Chat Completions API

  • 需要应用聊天模板格式化消息
  • 通过 OpenAIServingChat 处理请求
  • 调用 create_chat_completion() 方法,内部会先渲染聊天请求 3

3. Embeddings API

  • 使用池化模型处理输入
  • 返回向量表示而非文本
  • 通过不同的 pooling 任务类型处理 4

底层引擎统一接口

无论哪种 API,最终都会通过 EngineClient 接口调用到底层的 EngineCore

  • 输入处理:将不同 API 的请求转换为统一的 EngineCoreRequest
  • 调度执行:由 Scheduler 决定请求的执行顺序
  • 模型推理:在 GPU Worker 中执行实际的模型前向传播
  • 输出处理:将模型输出格式化为对应 API 的响应格式 5

Notes

  • 所有 API 都共享相同的底层推理引擎,区别主要在于输入预处理和输出后处理
  • Chat API 需要额外的聊天模板处理步骤
  • Embeddings API 使用池化模式而非生成模式
  • API 服务器通过 ZMQ 与引擎核心进程通信,支持多进程部署

Wiki pages you might want to explore:

Citations

File: docs/serving/openai_compatible_server.md (L43-63)

## Supported APIs

We currently support the following OpenAI APIs:

- [Completions API](#completions-api) (`/v1/completions`)
    - Only applicable to [text generation models](../models/generative_models.md).
    - *Note: `suffix` parameter is not supported.*
- [Responses API](#responses-api) (`/v1/responses`)
    - Only applicable to [text generation models](../models/generative_models.md).
- [Chat Completions API](#chat-api) (`/v1/chat/completions`)
    - Only applicable to [text generation models](../models/generative_models.md) with a [chat template](../serving/openai_compatible_server.md#chat-template).
    - *Note: `user` parameter is ignored.*
    - *Note:* Setting the `parallel_tool_calls` parameter to `false` ensures vLLM only returns zero or one tool call per request. Setting it to `true` (the default) allows returning more than one tool call per request. There is no guarantee more than one tool call will be returned if this is set to `true`, as that behavior is model dependent and not all models are designed to support parallel tool calls.
- [Embeddings API](#embeddings-api) (`/v1/embeddings`)
    - Only applicable to [embedding models](../models/pooling_models.md).
- [Transcriptions API](#transcriptions-api) (`/v1/audio/transcriptions`)
    - Only applicable to [Automatic Speech Recognition (ASR) models](../models/supported_models.md#transcription).
- [Translation API](#translations-api) (`/v1/audio/translations`)
    - Only applicable to [Automatic Speech Recognition (ASR) models](../models/supported_models.md#transcription).
- [Realtime API](#realtime-api) (`/v1/realtime`)
    - Only applicable to [Automatic Speech Recognition (ASR) models](../models/supported_models.md#transcription).

File: vllm/entrypoints/openai/completion/serving.py (L1-44)

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

import asyncio
import time
from collections.abc import AsyncGenerator, AsyncIterator
from collections.abc import Sequence as GenericSequence
from typing import cast

from fastapi import Request

from vllm.engine.protocol import EngineClient
from vllm.entrypoints.logger import RequestLogger
from vllm.entrypoints.openai.completion.protocol import (
    CompletionLogProbs,
    CompletionRequest,
    CompletionResponse,
    CompletionResponseChoice,
    CompletionResponseStreamChoice,
    CompletionStreamResponse,
)
from vllm.entrypoints.openai.engine.protocol import (
    ErrorResponse,
    PromptTokenUsageInfo,
    RequestResponseMetadata,
    UsageInfo,
)
from vllm.entrypoints.openai.engine.serving import (
    GenerationError,
    OpenAIServing,
    clamp_prompt_logprobs,
)
from vllm.entrypoints.openai.models.serving import OpenAIServingModels
from vllm.entrypoints.utils import get_max_tokens, should_include_usage
from vllm.exceptions import VLLMValidationError
from vllm.inputs.data import ProcessorInputs
from vllm.logger import init_logger
from vllm.logprobs import Logprob
from vllm.outputs import RequestOutput
from vllm.sampling_params import BeamSearchParams, SamplingParams
from vllm.tokenizers import TokenizerLike
from vllm.utils.async_utils import merge_async_iterators
from vllm.utils.collection_utils import as_list

File: vllm/entrypoints/openai/chat_completion/serving.py (L279-314)

    async def create_chat_completion(
        self,
        request: ChatCompletionRequest,
        raw_request: Request | None = None,
    ) -> AsyncGenerator[str, None] | ChatCompletionResponse | ErrorResponse:
        """
        Chat Completion API similar to OpenAI's API.

        See https://platform.openai.com/docs/api-reference/chat/create
        for the API specification. This API mimics the OpenAI
        Chat Completion API.
        """
        # Streaming response
        tokenizer = self.renderer.tokenizer
        assert tokenizer is not None
        reasoning_parser: ReasoningParser | None = None
        if self.reasoning_parser_cls:
            # Pass the same chat template kwargs as used in tokenization
            chat_template_kwargs = self._prepare_extra_chat_template_kwargs(
                request.chat_template_kwargs,
                self.default_chat_template_kwargs,
            )
            reasoning_parser = self.reasoning_parser_cls(
                tokenizer,
                chat_template_kwargs=chat_template_kwargs,  # type: ignore[call-arg]
            )
        result = await self.render_chat_request(request)
        if isinstance(result, ErrorResponse):
            return result

        conversation, engine_prompts = result

        request_id = (
            f"chatcmpl-{self._base_request_id(raw_request, request.request_id)}"
        )

File: docs/models/pooling_models.md (L161-184)

### `LLM.encode`

The [encode][vllm.LLM.encode] method is available to all pooling models in vLLM.

!!! note
    Please use one of the more specific methods or set the task directly when using `LLM.encode`:

    - For embeddings, use `LLM.embed(...)` or `pooling_task="embed"`.
    - For classification logits, use `LLM.classify(...)` or `pooling_task="classify"`.
    - For similarity scores, use `LLM.score(...)`.
    - For rewards, use `LLM.reward(...)` or `pooling_task="token_classify"`.
    - For token classification, use `pooling_task="token_classify"`.
    - For multi-vector retrieval, use `pooling_task="token_embed"`.
    - For IO Processor Plugins, use `pooling_task="plugin"`.

```python
from vllm import LLM

llm = LLM(model="intfloat/e5-small", runner="pooling")
(output,) = llm.encode("Hello, my name is", pooling_task="embed")

data = output.outputs.data
print(f"Data: {data!r}")

**File:** docs/design/arch_overview.md (L151-170)
```markdown
the vLLM system, handling model inference and asynchronous request processing.

![LLMEngine Diagram](../assets/design/arch_overview/llm_engine.excalidraw.png)

### LLMEngine

The `LLMEngine` class is the core component of the vLLM engine. It is
responsible for receiving requests from clients and generating outputs from the
model. The `LLMEngine` includes input processing, model execution (possibly
distributed across multiple hosts and/or GPUs), scheduling, and output
processing.

- **Input Processing**: Handles tokenization of input text using the specified
  tokenizer.
- **Scheduling**: Chooses which requests are processed in each step.
- **Model Execution**: Manages the execution of the language model, including
  distributed execution across multiple GPUs.
- **Output Processing**: Processes the outputs generated by the model, decoding the
  token IDs from a language model into human-readable text.

好的,这个问题问得非常好,它直指OpenAI API及其模仿者(如vLLM的OpenAI兼容服务器)中最核心的两种交互模式。

v1/completionsv1/chat/completions 的主要区别在于它们设计的交互模式输入/输出的数据结构不同。

  • /v1/completions: 文本补全 (Text Completion) 模式。它像一个强大的自动补全工具。
  • /v1/chat/completions: 聊天补全 (Chat Completion) 模式。它专门为多轮对话场景设计,能理解角色和上下文。

下面我们通过定义、使用场景和具体例子来详细解释。


1. /v1/completions (文本补全)

定义和理念

这种模式把语言模型看作一个文本续写引擎。你给它一段文本(叫做prompt),它的任务就是预测接下来最可能出现的文本,并把它们“补全”出来。

  • 输入: 一个简单的字符串 (prompt)。
  • 交互模型: 单次、无上下文的“续写”。它不原生支持多轮对话的角色扮演(比如“用户”和“助手”)。
  • 适用模型: 早期模型如 text-davinci-003 主要使用这个接口。现代的聊天模型虽然也可能支持,但通常不推荐用这种方式。
使用场景
  • 文本生成: 写一篇文章的开头,让模型续写。
  • 代码补全: 给出函数签名和注释,让模型写函数体。
  • 简单的问答: 将问题直接作为prompt,期望模型补全答案。
  • 文本分类/摘要: 通过精心设计的“指令式prompt”来完成任务。例如: "将以下文本总结为一句话:[长文本] 摘要:",然后模型会补全摘要内容。
举例 (使用curl)

假设我们要让模型续写一句诗 “Roses are red, violets are blue, …”:

curl http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "text-davinci-003",
    "prompt": "Roses are red, violets are blue,",
    "max_tokens": 10,
    "temperature": 0.7
  }'

可能的返回结果 (response):

{
  "id": "cmpl-...",
  "object": "text_completion",
  "created": 1677652288,
  "model": "text-davinci-003",
  "choices": [
    {
      "text": " sugar is sweet, and so are you.",
      "index": 0,
      "logprobs": null,
      "finish_reason": "length"
    }
  ],
  "usage": { ... }
}

关键点: 输入是"prompt": "一个字符串",输出的补全内容在choices[0].text里。


2. /v1/chat/completions (聊天补全)

定义和理念

这是为对话场景量身定做的模式。它把语言模型看作一个聊天机器人。你可以定义不同的角色(系统、用户、助手),并提供一个完整的对话历史。

  • 输入: 一个消息列表 (messages),列表中的每个对象都有 rolecontent 两个字段。
  • 交互模型: 原生支持多轮对话。模型能理解对话的上下文、谁说了什么话。
  • 适用模型: 现代的主流聊天模型,如GPT-3.5, GPT-4, Llama, Qwen等,都主要使用这个接口。
常见角色 (role)
  • system: "系统"角色。用于给模型设定总体的指令、个性和行为准则。例如:“你是一个乐于助人的AI助手。”
  • user: "用户"角色。代表真实用户的提问或输入。
  • assistant: "助手"角色。代表AI模型之前的回复。将模型的历史回复也加入messages列表,可以实现多轮对话的记忆。
使用场景
  • 聊天机器人/AI客服: 几乎所有对话式应用都使用此模式。
  • 复杂的指令遵循: system prompt可以用来下达非常复杂的指令。
  • 多轮问答: 用户可以追问,模型能根据之前的对话历史来回答。
  • 角色扮演: 在system prompt中设定一个角色,模型会以该角色进行对话。
举例 (使用curl)

假设我们想进行一次简单的对话。

第一轮对话:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {"role": "system", "content": "你是一个知识渊博的历史学家。"},
      {"role": "user", "content": "你好,请问法国大革命是什么时候开始的?"}
    ],
    "temperature": 0.7
  }'

可能的返回结果 (response):

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-3.5-turbo",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "你好!法国大革命通常被认为是在1789年7月14日攻占巴士底狱时开始的。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": { ... }
}

关键点: 输入是"messages": [一个对象列表],输出的回复在choices[0].message.content里。


总结对比

特性 /v1/completions (文本补全) /v1/chat/completions (聊天补全)
设计理念 文本续写引擎 聊天机器人
核心输入 prompt: 一个字符串 messages: 一个消息对象列表
数据结构 简单,扁平 结构化,包含rolecontent
对话能力 不原生支持,需要手动构建复杂的prompt来模拟 原生支持,能清晰地区分角色和对话历史
上下文管理 困难,需要将整个对话历史拼接成一个长字符串 简单,只需将新的用户提问和AI的回复追加到messages列表中
主要适用模型 早期模型 (text-davinci-003) 现代聊天模型 (gpt-4, llama, qwen等)
推荐度 已过时,不推荐 (Legacy API) 当前主流,强烈推荐

对于vLLM的OpenAI兼容服务器来说,它模仿了这两种API接口,让你能够用同样的方式与通过vLLM部署的开源模型进行交互。尽管completions接口仍然可用,但几乎所有现代应用都应该优先使用功能更强大、结构更清晰的chat/completions接口。

在这里插入图片描述
/v1/completions/v1/chat/completions 是 vLLM 提供的两个不同的推理接口,它们的区别主要在于输入格式适用场景,类似于 OpenAI API 的设计。

特性 /v1/completions /v1/chat/completions
核心用途 纯文本补全(Text Completion) 多轮对话补全(Chat Completion)
输入格式 单个字符串 prompt 消息列表 messages,每条消息包含 rolecontent
角色支持 无角色概念 支持 systemuserassistant 等角色
适用模型 适合基础语言模型(如 LLaMA base) 适合经过指令或对话微调的模型(如 LLaMA-chat、Qwen-Chat)
内部处理 直接传入模型生成 会根据模型对应的聊天模板将 messages 转换为单个提示字符串(加上特殊标记)
典型场景 续写文章、代码生成、翻译等 多轮对话、客服问答、角色扮演等

详细解释

  1. 输入结构不同

    • completions 只需要一个简单的 prompt 字符串:
      {
        "model": "model-name",
        "prompt": "What is the capital of France?"
      }
      
    • chat/completions 需要结构化的消息列表,每条消息明确指定谁在说话:
      {
        "model": "model-name",
        "messages": [
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "What is the capital of France?"}
        ]
      }
      
  2. 内部模板处理

    • vLLM 在收到 /v1/chat/completions 请求时,会自动根据模型自带的聊天模板(如 ChatML、Llama‑2 模板)将 messages 列表拼接成一个完整的提示字符串,并添加角色标记(如 <|im_start|>user\n...)。这个过程对用户透明。
    • /v1/completions 则不会做任何模板处理,直接将 prompt 喂给模型。
  3. 适用模型

    • 基础预训练模型(如 LLaMA‑2‑7b)通常只支持 /v1/completions,因为它们没有经过指令或对话微调,不理解角色标记。
    • 对话模型(如 LLaMA‑2‑7b‑chat)虽然也可以调用 /v1/completions,但效果可能不如 /v1/chat/completions,因为缺少模板引导。
  4. 返回格式

    • 两个端点的返回结构基本一致,都遵循 OpenAI 格式,包含生成的文本、token 计数等。但 chat/completions 的响应中 choices[0].message 包含角色和内容,而 completionschoices[0].text 直接是生成的文本。

使用建议

  • 如果你在开发对话式应用,优先使用 /v1/chat/completions,这样 vLLM 会帮你处理好聊天模板,避免手动拼接错误。
  • 如果你只需要简单的文本续写或生成,且不希望引入任何特殊标记,使用 /v1/completions 更直接。

在 vLLM 中,这两个端点都默认开启(如果模型支持),你可以根据任务需求灵活选择。

模型差异:某些模型可能只支持 completions(如 base 模型),而 chat 模型经过微调以适应对话格式,内部会应用聊天模板。 举例说明?

在这里插入图片描述
我们以 Meta 的 Llama 2 系列模型为例,来说明 Base 模型与 Chat 模型在使用 /v1/completions/v1/chat/completions 时的差异,以及聊天模板如何起作用。


1. 背景

  • Base 模型(如 meta-llama/Llama-2-7b-hf)是在大规模文本语料上预训练的,擅长文本续写、语言建模,但没有专门针对对话格式进行微调。
  • Chat 模型(如 meta-llama/Llama-2-7b-chat-hf)是在 Base 模型基础上,通过对话数据(包含角色标记如 [INST][/INST])进一步微调得到的,能够更好地理解和遵循多轮对话的格式。

在 vLLM 中,/v1/chat/completions 端点会根据模型自带的聊天模板,自动将 messages 列表转换成模型期望的输入格式(添加特殊标记)。而 /v1/completions 端点则直接使用用户提供的 prompt,不做任何模板处理。


2. 具体示例

假设我们向两个模型分别发送同一个对话请求:
用户询问“What is the capital of France?”,我们希望模型回答“Paris”。

情况 A:使用 Base 模型(Llama-2-7b-hf

调用 /v1/completions

curl http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-2-7b-hf",
    "prompt": "Q: What is the capital of France?\nA:",
    "max_tokens": 10
  }'

预期输出(可能):
模型可能会生成类似“Paris is the capital of France.”的文本,因为它只是在续写“A:”后面的内容。由于 Base 模型见过很多问答对,这种方式有时也能工作,但不够鲁棒。

调用 /v1/chat/completions(不推荐用于 Base 模型)

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-2-7b-hf",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ],
    "max_tokens": 10
  }'

预期输出
Base 模型没有针对 role 字段进行训练,所以 vLLM 虽然也会尝试应用一个默认模板(通常只是简单拼接消息),但生成的内容可能很奇怪,比如重复“user:”或输出无关内容,因为模型不理解这些特殊标记的含义。


情况 B:使用 Chat 模型(Llama-2-7b-chat-hf

调用 /v1/chat/completions(正确方式)

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-2-7b-chat-hf",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ],
    "max_tokens": 10
  }'

内部发生了什么
vLLM 会读取模型对应的聊天模板(通常保存在 tokenizer_config.json 中)。对于 Llama-2-chat,模板大致是:

<s>[INST] <<SYS>>
{{ system_prompt }}
<</SYS>>

{{ user_message }} [/INST]

由于我们没有提供 system 消息,最终构造的输入提示(prompt)会变成:

<s>[INST] What is the capital of France? [/INST]

这个格式正是 Chat 模型微调时使用的输入格式。因此模型能准确理解这是一个用户问题,并生成助手回答。

预期输出
模型将生成类似“Paris”或“The capital of France is Paris.”的回答。

调用 /v1/completions(手动构造模板)

curl http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-2-7b-chat-hf",
    "prompt": "<s>[INST] What is the capital of France? [/INST]",
    "max_tokens": 10
  }'

这也能得到正确的回答,但需要手动添加特殊标记。如果忘记添加,比如只给 "What is the capital of France?",模型可能不会把它当作一个用户指令,生成的内容可能偏离预期(例如继续提问或无关续写)。


3. 为什么 /v1/chat/completions 对 Chat 模型更好?

  • 自动模板处理:避免手动拼接角色标记,减少出错。
  • 支持多轮对话messages 列表可以包含多条历史消息,vLLM 会按照模板正确组织整个对话历史(例如交替的 [INST][/INST] 块)。
  • 模型适配性:不同的 Chat 模型可能使用不同的模板(如 ChatML、Zephyr 等),vLLM 根据模型配置自动选择,无需用户关心。

4. 总结

模型类型 推荐端点 原因
Base 模型 /v1/completions 直接文本续写,无角色概念;使用 /v1/chat/completions 可能得到乱码。
Chat 模型 /v1/chat/completions 自动应用聊天模板,生成符合对话格式的回答,更准确。

通过这个例子,你可以理解为什么模型类型和 API 端点的匹配如此重要——它直接关系到生成结果的质量。

/v1/chat/completions 可以用多轮对话? 怎么用的?举例说明?

当然可以!/v1/chat/completions 这个接口就是为了多轮对话而设计的,这是它相比于旧的 /v1/completions 接口最大的优势。

实现多轮对话的核心思想是:在每一次新的请求中,都把到目前为止的全部对话历史(包括系统指令、用户的所有提问、以及AI的所有回答)作为一个完整的 messages 列表发送给模型。

这样,模型在生成新的回答时,就能“看到”并“理解”之前的上下文。


多轮对话的实现步骤

  1. 初始化对话 (第一轮):

    • 构建一个 messages 列表,通常包含一个 system 消息(可选,但推荐)和一个 user 消息。
    • 将这个列表发送到 /v1/chat/completions 接口。
    • 收到模型的回复(一个 assistant 角色的消息)。
    • 关键:将你刚刚发送的 user 消息和收到的 assistant 回复都保存下来。
  2. 进行下一轮对话:

    • 当用户输入新的问题时,构建一个新的 messages 列表。
    • 这个新列表必须包含之前保存的所有消息,然后在其末尾追加上用户的新问题(一个新的 user 消息)。
    • 将这个更长messages 列表发送到接口。
    • 收到模型新的回复。
    • 关键:再次将用户的这个新问题和模型的新回复追加到你保存的对话历史中。
  3. 重复步骤2: 只要对话继续,就不断地将新的“一问一答”追加到历史记录中,然后将完整的历史记录作为下一次请求的 messages


举例说明:一个关于旅行计划的多轮对话

假设一个用户正在计划去巴黎旅行,我们来看看这个对话是如何通过API调用实现的。

第一轮:用户询问天气

你的应用程序需要做的事情:

  1. 构建初始的 messages 列表。
  2. 发送API请求。

请求 (Request 1):

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {
        "role": "system", 
        "content": "你是一个友好的旅行助手。"
      },
      {
        "role": "user", 
        "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"
      }
    ]
  }'

模型返回的响应 (Response 1):

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"
      }
    }
  ]
}

你的应用程序现在需要保存的对话历史:

# 在你的代码中,对话历史可能是一个这样的列表
conversation_history = [
    {"role": "system", "content": "你是一个友好的旅行助手。"},
    {"role": "user", "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"},
    {"role": "assistant", "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"}
]

第二轮:用户根据天气追问穿衣建议

现在,用户根据模型的回答,提出了一个新的问题:“那我应该带些什么衣服呢?”

你的应用程序需要做的事情:

  1. 在之前保存的 conversation_history 基础上,追加用户的新问题。
  2. 发送新的API请求,这次的 messages 列表更长了。

请求 (Request 2):

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {
        "role": "system", 
        "content": "你是一个友好的旅行助手。"
      },
      {
        "role": "user", 
        "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"
      },
      {
        "role": "assistant", 
        "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"
      },
      {
        "role": "user", 
        "content": "太好了!那我应该带些什么衣服呢?"
      }
    ]
  }'

模型返回的响应 (Response 2):

模型因为看到了之前的对话(特别是关于气温的部分),所以能给出非常相关的建议。

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "根据那个天气情况,我建议你带一些可以叠穿的衣物。比如:几件T恤或薄款长袖衫作为打底,一件毛衣或开衫用于保暖,再加上你之前提到的那件薄外套。白天暖和的时候可以单穿,早晚凉了可以随时加上。另外,巴黎很适合步行,别忘了一双舒适的鞋子!"
      }
    }
  ]
}

你的应用程序现在更新对话历史:

现在,conversation_history 列表会变得更长,包含了第二轮的问答。这个过程会一直持续下去。

总结

  • 核心机制: 每次请求都发送完整的对话历史。
  • 角色分工: system 设定基调,user 提问,assistant 是模型的历史回答。
  • 状态管理: 你需要在你的应用程序(客户端或服务器端)维护这个 messages 列表,并在每次交互后更新它。模型本身是无状态的,它不记得之前的对话,它的“记忆”完全来自于你每次传递给它的 messages 列表。

这就是 /v1/chat/completions 实现多轮对话的全部秘密,简单而强大。

当然可以!/v1/chat/completions 这个接口就是为了多轮对话而设计的,这是它相比于旧的 /v1/completions 接口最大的优势。

实现多轮对话的核心思想是:在每一次新的请求中,都把到目前为止的全部对话历史(包括系统指令、用户的所有提问、以及AI的所有回答)作为一个完整的 messages 列表发送给模型。

这样,模型在生成新的回答时,就能“看到”并“理解”之前的上下文。


多轮对话的实现步骤

  1. 初始化对话 (第一轮):

    • 构建一个 messages 列表,通常包含一个 system 消息(可选,但推荐)和一个 user 消息。
    • 将这个列表发送到 /v1/chat/completions 接口。
    • 收到模型的回复(一个 assistant 角色的消息)。
    • 关键:将你刚刚发送的 user 消息和收到的 assistant 回复都保存下来。
  2. 进行下一轮对话:

    • 当用户输入新的问题时,构建一个新的 messages 列表。
    • 这个新列表必须包含之前保存的所有消息,然后在其末尾追加上用户的新问题(一个新的 user 消息)。
    • 将这个更长messages 列表发送到接口。
    • 收到模型新的回复。
    • 关键:再次将用户的这个新问题和模型的新回复追加到你保存的对话历史中。
  3. 重复步骤2: 只要对话继续,就不断地将新的“一问一答”追加到历史记录中,然后将完整的历史记录作为下一次请求的 messages


举例说明:一个关于旅行计划的多轮对话

假设一个用户正在计划去巴黎旅行,我们来看看这个对话是如何通过API调用实现的。

第一轮:用户询问天气

你的应用程序需要做的事情:

  1. 构建初始的 messages 列表。
  2. 发送API请求。

请求 (Request 1):

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {
        "role": "system", 
        "content": "你是一个友好的旅行助手。"
      },
      {
        "role": "user", 
        "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"
      }
    ]
  }'

模型返回的响应 (Response 1):

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"
      }
    }
  ]
}

你的应用程序现在需要保存的对话历史:

# 在你的代码中,对话历史可能是一个这样的列表
conversation_history = [
    {"role": "system", "content": "你是一个友好的旅行助手。"},
    {"role": "user", "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"},
    {"role": "assistant", "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"}
]

第二轮:用户根据天气追问穿衣建议

现在,用户根据模型的回答,提出了一个新的问题:“那我应该带些什么衣服呢?”

你的应用程序需要做的事情:

  1. 在之前保存的 conversation_history 基础上,追加用户的新问题。
  2. 发送新的API请求,这次的 messages 列表更长了。

请求 (Request 2):

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {
        "role": "system", 
        "content": "你是一个友好的旅行助手。"
      },
      {
        "role": "user", 
        "content": "你好,我想计划去巴黎旅行。下周那里的天气怎么样?"
      },
      {
        "role": "assistant", 
        "content": "你好!很高兴能帮你规划巴黎之旅。查询了一下,下周巴黎的天气以晴间多云为主,气温大约在15到22摄氏度之间,非常适合旅行。不过早晚温差较大,建议带上一件薄外套。"
      },
      {
        "role": "user", 
        "content": "太好了!那我应该带些什么衣服呢?"
      }
    ]
  }'

模型返回的响应 (Response 2):

模型因为看到了之前的对话(特别是关于气温的部分),所以能给出非常相关的建议。

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "根据那个天气情况,我建议你带一些可以叠穿的衣物。比如:几件T恤或薄款长袖衫作为打底,一件毛衣或开衫用于保暖,再加上你之前提到的那件薄外套。白天暖和的时候可以单穿,早晚凉了可以随时加上。另外,巴黎很适合步行,别忘了一双舒适的鞋子!"
      }
    }
  ]
}

你的应用程序现在更新对话历史:

现在,conversation_history 列表会变得更长,包含了第二轮的问答。这个过程会一直持续下去。

总结

  • 核心机制: 每次请求都发送完整的对话历史。
  • 角色分工: system 设定基调,user 提问,assistant 是模型的历史回答。
  • 状态管理: 你需要在你的应用程序(客户端或服务器端)维护这个 messages 列表,并在每次交互后更新它。模型本身是无状态的,它不记得之前的对话,它的“记忆”完全来自于你每次传递给它的 messages 列表。

这就是 /v1/chat/completions 实现多轮对话的全部秘密,简单而强大。

你提出了一个非常深刻且关键的问题!我们刚才讨论的“在每次请求中传递完整对话历史”是如何使用一个已经支持多轮对话的模型。而你的问题更进一步,触及了如何从零开始,通过指令微tuning(SFT)让一个基础模型(Base Model)学会进行多轮对话

这背后依赖于一个核心思想:将多轮对话本身也看作一种“指令”,并通过构造特定的数据格式来教会模型遵循这种“对话指令”。

下面是详细的步骤和解释:

1. 核心理念:将对话视为“单回合”的续写任务

尽管我们看到的是多轮对话,但在训练时,模型处理的仍然是一个**“单回合”的预测任务**。这个任务是:给定一段包含完整对话历史的文本,预测并生成下一句“助手”的回答。

模型并不是真的在“记忆”或“交互”,它只是在学习一个非常复杂的模式匹配:当看到形如 “A说了X,B说了Y,A又说了Z” 这样的文本时,接下来B应该说什么。

2. 数据集构造是关键

要让模型学会这个模式,最关键的一步是构造符合特定格式的指令微调数据集。这个数据集中的每一条样本,都代表了一次完整的“续写”任务。

数据格式

一条训练数据通常是一个包含“输入”(input)和“输出”(output)的JSON对象,但它的内容需要被精心设计。对于多轮对话,这个input部分就是格式化后的对话历史。

假设我们有一段原始的多轮对话:

User: 你好!
Assistant: 你好!有什么可以帮你的吗?
User: 帮我写一首关于春天的诗。
Assistant: 当然!“春风拂面绿意浓,花开遍野笑语中。燕归巢时蝶双舞,一派生机万物荣。”

为了训练模型在第二轮回答“当然!…”,我们需要构造如下的训练样本:

训练样本 (伪代码):

{
  "instruction": "请根据以下对话历史,生成助手的下一句回答。", // 这个指令通常是隐式的,通过格式来体现
  "input": "
    <|im_start|>system
    你是一个乐于助人的AI助手。<|im_end|>
    <|im_start|>user
    你好!<|im_end|>
    <|im_start|>assistant
    你好!有什么可以帮你的吗?<|im_end|>
    <|im_start|>user
    帮我写一首关于春天的诗。<|im_end|>
    <|im_start|>assistant
    ", // 输入在这里结束,提示模型开始生成
  "output": "
    当然!“春风拂面绿意浓,花开遍野笑语中。燕归巢时蝶双舞,一派生机万物荣。”<|im_end|>
    "
}

关键点解析:

  1. 特殊Token/格式化:

    • 我们使用特殊的标记(如Qwen的<|im_start|><|im_end|>,或Llama的[INST][/INST])来清晰地界定每一轮对话的开始、结束以及说话者的角色(system, user, assistant)。
    • 这种格式化的文本,我们称之为对话模板 (Chat Template)
    • 模型在训练时,会学到这些特殊token的含义。它知道看到 <|im_start|>user 就意味着接下来是用户的话,看到 <|im_start|>assistant 就意味着轮到它自己说话了。
  2. 构造输入 (input):

    • 输入文本包含了从对话开始到当前助手需要回答之前的全部内容。
    • 输入的结尾一定是助手的起始标记,如 <|im_start|>assistant。这相当于一个强烈的信号,告诉模型:“现在轮到你接话了!”
  3. 构造输出 (output):

    • 输出文本就是助手应该生成的那一句回答,并且通常也以结束标记 <|im_end|> 结尾。
  4. 损失函数计算 (Loss Calculation):

    • 在训练时,模型会接收完整的 input + output 文本。
    • 我们会使用一个损失掩码 (Loss Mask),告诉模型只需要计算 output 部分的预测损失。也就是说,模型因为没有正确预测出用户历史说过的话(input部分)而受到的“惩罚”是0。它唯一的任务就是学习如何正确地生成 output 部分。

3. 训练过程

  1. 准备大量数据: 收集或构造数万到数百万条这样的多轮对话样本。这些数据可以来自人工标注、现有的开源对话数据集,或者用更强的模型(如GPT-4)生成。

  2. 处理数据集: 将每一段多轮对话,按照上面的逻辑,转换成多条训练样本。例如,一个三轮的对话可以生成三条训练数据:

    • 样本1: input = 第1轮user, output = 第1轮assistant
    • 样本2: input = 第1轮user+assistant + 第2轮user, output = 第2轮assistant
    • 样本3: input = 第1轮user+assistant + 第2轮user+assistant + 第3轮user, output = 第3轮assistant
  3. 进行指令微调 (SFT):

    • 选择一个预训练好的基础模型(Base LLM)。
    • 使用处理好的数据集对这个模型进行微调。
    • 在微调过程中,模型会更新其权重,以最小化在 output 部分的预测损失。

4. 训练完成后的效果

经过这样大量的“看图说话”式的训练后,模型就学会了:

  • 识别对话模板中的特殊标记。
  • 理解不同角色的含义。
  • 根据长篇的、结构化的对话历史上下文。
  • 在轮到它说话时(即看到 <|im_start|>assistant),生成一个符合角色、上下文和指令的、高质量的回答。

此时,这个基础模型就从一个只会“续写”的引擎,变成了一个真正懂得如何“对话”的聊天模型。当我们通过 /v1/chat/completions 接口调用它时,API的后端会自动将我们发送的messages列表用同样的对话模板格式化成模型在训练时见过的样子,然后送给模型进行推理。

总结:指令微调实现多轮对话的本质,就是通过设计一种结构化的对话模板,将多轮对话任务巧妙地转化为大量的单回合续写任务,然后通过监督学习让模型掌握这种格式和模式

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐