问题背景:为什么官方文档和示例不一定可信?
在使用 vLLM(尤其是 reranker / score 这类非 OpenAI 标准接口)时,很多人会遇到这些问题:
-
路由很多(
/score、/v1/score、/rerank、/v1/rerank、/v2/rerank),仅确定部分路由,无法明确实际需要的路由及其调用格式 -
需要明确源码内部运行逻辑
-
Swagger UI 的 Example 示例可能是错的或混合的
-
不同版本、不同 task / runner 下,接口 schema 会变化
-
调接口只能靠"试参数",非常低效
这类问题的本质不是"不会用接口",而是:
👉 不知道"当前实例真实生效的接口契约是什么"
工程上,这类问题通常称为:
接口溯源 / 接口契约发现(API Tracing / API Contract Discovery)
下面给出一套不依赖猜模块名、不依赖文档正确性的通用白盒排查流程。
通用排查逻辑概览
整个流程可以总结为一句话:
从日志确定入口文件 → 在源码中定位路由 → 沿调用链找到 Request 类型 → 确认真实实现逻辑
这套方法不仅适用于 vLLM,所有基于 FastAPI / Flask / Starlette 的 Python 服务库都适用。
目录
[Step 1:确认模块安装路径(包根)](#Step 1:确认模块安装路径(包根))
[Step 2:通过运行日志反推出"关键入口文件"(核心步骤)](#Step 2:通过运行日志反推出“关键入口文件”(核心步骤))
[Step 3:从包根向下定位 api_server.py](#Step 3:从包根向下定位 api_server.py)
[Step 4:在入口文件中建立"路由索引"](#Step 4:在入口文件中建立“路由索引”)
[方式 A:只凭 FastAPI 特征定位](#方式 A:只凭 FastAPI 特征定位)
[方式 B:直接在 api_server.py 内全文搜索](#方式 B:直接在 api_server.py 内全文搜索)
[Step 5:以 /v1/score 为例,沿调用链找到真实接口契约](#Step 5:以 /v1/score 为例,沿调用链找到真实接口契约)
[1️⃣ 确认路由入口](#1️⃣ 确认路由入口)
[2️⃣ 确认请求类型(接口"怎么传参"的权威定义)](#2️⃣ 确认请求类型(接口“怎么传参”的权威定义))
[3️⃣ 确认真实实现逻辑](#3️⃣ 确认真实实现逻辑)
[批量请求(text_2 支持数组)](#批量请求(text_2 支持数组))
[附:vLLM 仅凭 OpenAPI "明确调用参数"的方法](#附:vLLM 仅凭 OpenAPI “明确调用参数”的方法)
[1)先拿到 /v1/score 请求体 schema 引用](#1)先拿到 /v1/score 请求体 schema 引用)
[2)导出 ScoreRequest 的完整字段定义(权威参数表)](#2)导出 ScoreRequest 的完整字段定义(权威参数表))
[同理导出 rerank 的 schema](#同理导出 rerank 的 schema)
Step 1:确认模块安装路径(包根)
首先确认 vLLM 在当前 Python 环境中的安装位置:
python
python -c "import vllm,inspect,os; import vllm as s; print(os.path.abspath(inspect.getfile(s)))"
输出类似:
python
/home/user/.anaconda3/envs/env_name/lib/python3.10/site-packages/vllm/__init__.py
这一结果只说明一件事:
- vLLM 的包根目录在
.../site-packages/vllm/
⚠️ 注意
__init__.py永远不可能 是 HTTP 接口定义位置真正的接口一定在"服务入口层"
Step 2:通过运行日志反推出"关键入口文件"(核心步骤)
这是非常关键、也最容易被忽略的一步。
vLLM 在启动 HTTP Server 时,会打印类似日志:
python
(APIServer pid=81790) INFO [api_server.py:1977] vLLM API server version 0.11.2
这个日志信息本身已经暴露了关键线索:
-
模块名:
api_server.py -
角色:
APIServer -
说明这是 HTTP API Server 的入口实现
由此可以做出合理推断:
在 vLLM 的包目录下,一定存在一个
api_server.py,并且它是 API 的核心入口文件
于是,下一步不是"猜模块路径",而是:
👉 在 vLLM 包目录中查找 api_server.py
Step 3:从包根向下定位 api_server.py
既然已经知道包根路径是:
bash
.../site-packages/vllm/
那么只需要在这个目录下定位 api_server.py 即可:
bash
find /home/user/.anaconda3/envs/env_name/lib/python3.10/site-packages/vllm \
-name "api_server.py"
可以得到:
bash
/home/user/.anaconda3/envs/env_name/lib/python3.10/site-packages/vllm/entrypoints/openai/api_server.py
/home/user/.anaconda3/envs/env_name/lib/python3.10/site-packages/vllm/entrypoints/api_server.py
到这一步,你已经完成了最困难的一件事:
✅ 确定了 HTTP API 的真实入口文件
后续所有分析,都基于这个文件展开。
Step 4:在入口文件中建立"路由索引"
打开 api_server.py,可以看到大量 FastAPI 路由定义:
python
@router.post("/v1/score", ...)
async def create_score_v1(request: ScoreRequest, raw_request: Request):
return await create_score(request, raw_request)
即使你一开始不知道有哪些路由,也可以通过以下方式快速确认:
方式 A:只凭 FastAPI 特征定位
bash
grep -RInE "add_api_route|@router|/v1" \
/home/user/.anaconda3/envs/env_name/lib/python3.10/site-packages/vllm
方式 B:直接在 api_server.py 内全文搜索
搜索关键字:
-
/v1 -
@router.post -
@router.get
这样可以快速得到一份 URL → handler 函数 的映射索引。
Step 5:以 /v1/score 为例,沿调用链找到真实接口契约
1️⃣ 确认路由入口
在 api_server.py 中:
python
@router.post("/v1/score")
async def create_score_v1(request: ScoreRequest, raw_request: Request):
return await create_score(request, raw_request)
可以立即得出结论:
-
/v1/score的请求体类型是ScoreRequest -
create_score_v1只是外层兼容封装 -
真实逻辑在
create_score
2️⃣ 确认请求类型(接口"怎么传参"的权威定义)
顺着类型跳转到 ScoreRequest 的定义(通常在 protocol.py),可以看到类似:
python
class ScoreRequest(BaseModel):
model: str
text_1: str
text_2: Union[str, List[str]]
这一步直接回答了:
/v1/score到底支持哪些字段?为什么
queries / documents会报错?
因为真实 schema 只接受 text_1 / text_2。
3️⃣ 确认真实实现逻辑
继续跳转到:
python
return await create_score(request, raw_request)
进入 create_score 的实现(通常在 serving_score.py),可以看到:
-
是否支持 batch(
text_2为 list) -
如何构建 scoring batch
-
如何调用 engine / handler 执行 cross-encoder 计算
-
如何构造返回的 score 列表
这一步决定了接口的真实语义,而不是文档描述。
最终得到的正确调用方式(示例)
port替换成自己的端口号
单条请求
bash
curl -s http://127.0.0.1:port/v1/score \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-reranker-0.6b",
"text_1": "What is the capital of China?",
"text_2": "Beijing is the capital of China."
}'
批量请求(text_2 支持数组)
bash
curl -s http://127.0.0.1:port/v1/score \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-reranker-0.6b",
"text_1": "What is the capital of China?",
"text_2": ["Beijing is the capital of China.", "Paris is in France."]
}'
附:vLLM 仅凭 OpenAPI "明确调用参数"的方法
如果你不想读源码、也不想依赖 Swagger UI 的 Example(可能混合字段),vLLM 服务端本身提供了 OpenAPI 契约 ,它是"当前实例真实生效的接口定义",可以用它来精确确认请求参数字段、必填项与类型。
1)先拿到 /v1/score 请求体 schema 引用
port替换成自己需要调用的端口
bash
curl -s http://127.0.0.1:port/openapi.json \
| jq '.paths["/v1/score"].post.requestBody.content["application/json"].schema["$ref"]'
典型输出类似:
bash
"#/components/schemas/ScoreRequest"
这表示:/v1/score 的请求体由 ScoreRequest 这个 schema 定义。
2)导出 ScoreRequest 的完整字段定义(权威参数表)
bash
curl -s http://127.0.0.1:port/openapi.json \
| jq '.components.schemas.ScoreRequest' > ScoreRequest.json
生成的 ScoreRequest.json 里会明确写出:
-
必填字段(
required) -
字段类型(
type/oneOf等) -
字段描述(
description) -
默认值/约束(如果服务端提供)
建议:将该文件随部署一起保存(或在 CI 中留档),这样升级 vLLM 版本后可以 diff schema,避免 SDK/调用方被无声破坏。
同理导出 rerank 的 schema
bash
curl -s http://127.0.0.1:port/openapi.json \
| jq '.paths["/v1/rerank"].post.requestBody.content["application/json"].schema["$ref"]'
curl -s http://127.0.0.1:port/openapi.json \
| jq '.components.schemas.RerankRequest' > RerankRequest.json
总结:这套方法解决的是什么问题?
这套流程解决的不是"怎么调 vLLM 接口",而是一个更通用的工程问题:
当接口文档、示例或版本行为不可信时,如何从源码和运行信息中,确定真实生效的接口契约
其核心方法是:
-
从日志定位入口文件
-
从入口文件定位路由
-
从路由定位 Request 类型
-
从 Request 与 handler 定位真实实现逻辑
一旦掌握这套方法,不论是 vLLM,还是其他基于 FastAPI/Flask 的 Python 服务库,都可以在几分钟内完成接口"白盒确认"。