/v1/chat/completions 请求源码走读
/v1/chat/completions 请求源码走读
1. 目标
这份文档只回答一个问题:
当客户端发起一次 OpenAI 风格的 /v1/chat/completions 请求时,SGLang 的源码是如何一步步把这个请求送进运行时,再把生成结果流式返回的。
这不是完整架构文档,而是一条“请求主链路”的走读。
2. 先看结论版主链路
一次 /v1/chat/completions 请求的大致流向是:
http_server.py注册路由并接住 HTTP 请求serving_chat.py把ChatCompletionRequest转成内部GenerateReqInputtokenizer_manager.py负责标准化、tokenize、记录请求状态,并把请求发给调度器scheduler.py把 tokenized request 转成运行时Req,放入等待队列并决定何时执行- GPU 执行后,结果经 detokenizer 回到 tokenizer manager
serving_chat.py把内部输出再封装成 OpenAI 风格流式响应
用一句话概括:
协议适配层 -> 运行时入口层 -> 调度层 -> 执行层 -> 协议输出层
3. 第 1 跳:HTTP 路由入口
文件:
python/sglang/srt/entrypoints/http_server.py
关键位置:
@app.post("/v1/chat/completions", ...)openai_v1_chat_completions()
这一层做的事情其实很简单:
- FastAPI 收到请求
- 把请求体解析为
ChatCompletionRequest - 直接转发给
raw_request.app.state.openai_serving_chat.handle_request(...)
这里要注意一个关键点:
http_server.py不是聊天逻辑本体- 它只是把不同协议入口挂到不同 handler 上
也就是说,HTTP 路由层只负责“接住请求并把它交给对应服务对象”,并不直接做 prompt 组装和生成。
4. 第 2 跳:协议层把 OpenAI 请求转成运行时请求
文件:
python/sglang/srt/entrypoints/openai/serving_chat.py
这一层是 /v1/chat/completions 真正的协议适配层。
4.1 关键职责
它主要做三件事:
- 校验 OpenAI chat 请求是否合法
- 把
messages套用 chat template,变成最终 prompt - 把 OpenAI 风格参数转换成 SGLang 运行时内部的
GenerateReqInput
4.2 关键方法
重点看这些方法:
_convert_to_internal_request()_process_messages()_apply_conversation_template()_handle_streaming_request()_generate_chat_stream()
4.3 最关键的转换点
_convert_to_internal_request() 是这条链路里的关键桥梁。
它做了这些事:
- 先判断当前模型是不是多模态模型
- 调用
_process_messages()处理messages - 根据处理结果构造
sampling_params - 把 prompt、图像、视频、LoRA、stream、logprobs、priority 等字段组装成
GenerateReqInput
也就是说:
ChatCompletionRequest是 OpenAI 协议对象GenerateReqInput是 SGLang 运行时真正吃进去的内部请求对象
到了这一步,请求已经不再是“OpenAI chat 请求”,而是“运行时生成请求”。
4.4 prompt 是在哪里真正生成的
不是在 http_server.py,而是在 serving_chat.py 里。
尤其是:
_process_messages()_apply_conversation_template()
这两步会把 messages 套到模型对应的 conversation template 上,产出:
prompt- 或
prompt_ids - multimodal 数据
- stop 条件
这一步非常关键,因为 OpenAI 风格的 messages 结构本身并不是模型真正吃的格式,模型真正吃的是最终 prompt/token ids。
5. 第 3 跳:运行时入口层接管请求
文件:
python/sglang/srt/managers/tokenizer_manager.py
关键方法:
generate_request()
这是协议层进入运行时后的第一个核心入口。
5.1 generate_request() 做什么
当 serving_chat.py 进入 streaming 分支时,会执行:
self.tokenizer_manager.generate_request(adapted_request, raw_request)
这里的 adapted_request 就是刚刚构造好的 GenerateReqInput。
generate_request() 主要做这些事情:
normalize_batch_and_arguments()
统一单请求/批请求的内部表示_set_default_priority()
如果用户没传优先级,这里补默认值_validate_rid()
校验请求 id_req_stats_init()
初始化请求级统计信息request_logger.log_received_request(...)
记录收到请求_validate_and_resolve_lora(obj)
校验并解析 LoRA_tokenize_one_request(obj)或批量 tokenization
把请求真正转成 tokenized request_send_one_request(tokenized_obj)
发给 scheduler_wait_one_response(...)
异步等待返回结果并持续yield
所以这层的角色不是“只做 tokenizer”,而是:
- 运行时请求接入层
- 请求状态管理层
- tokenizer 层
- 与 scheduler 的上游接口层
6. 第 4 跳:内部请求对象长什么样
文件:
python/sglang/srt/managers/io_struct.py
关键结构:
GenerateReqInput
这个类就是协议层与运行时之间最重要的边界对象。
它里面会携带:
text/input_idsimage_data/video_data/audio_datasampling_paramsstreamreturn_logproblora_pathrouted_dp_rankprioritycustom_labels
也就是说,到了 GenerateReqInput 这一层,请求已经从“OpenAI chat schema”变成了“运行时通用生成 schema”。
这也是为什么 SGLang 可以同时兼容 OpenAI、Ollama、Anthropic 风格协议:
- 上层协议不同
- 但中间统一归一到运行时自己的请求对象
7. 第 5 跳:Scheduler 把 tokenized request 变成运行时 Req
文件:
python/sglang/srt/managers/scheduler.py
关键方法:
handle_generate_request()get_next_batch_to_run()
7.1 handle_generate_request() 做什么
当 tokenizer manager 把 tokenized request 发过来后,scheduler 先进入 handle_generate_request()。
它主要做这些事情:
- 判断是不是 session request
- 把
TokenizedGenerateReqInput转成内部运行时Req - 处理多模态输入展开
- 初始化
max_new_tokens - 校验输入长度
- 计算 logprob 相关设置
- 判断是否要进入 grammar queue
- 否则直接
_add_request_to_queue(req)
要注意这里的对象变化:
GenerateReqInput:协议层和 tokenizer manager 使用的通用请求对象TokenizedGenerateReqInput:已经 tokenized 的请求对象Req:scheduler 内部真正管理、排队、执行的运行时对象
所以 scheduler 接手时,请求又完成了一次“内部降维”:
- 从“用户请求对象”
- 变成“调度器任务对象”
7.2 为什么 handle_generate_request() 很重要
因为这是“协议世界”正式进入“调度世界”的边界。
从这里之后,调度器关心的就不再是:
- 用户发的是 chat 还是 completion
- OpenAI 参数名叫什么
而是开始关心:
- 这个请求有多少 input tokens
- 需要多少新 token
- 能不能进 batch
- 有没有 cache 命中
- 当前该 prefill 还是 decode
8. 第 6 跳:调度器决定现在跑 prefill 还是 decode
文件:
python/sglang/srt/managers/scheduler.py
关键方法:
get_next_batch_to_run()get_new_batch_prefill()update_running_batch()
get_next_batch_to_run() 是整个调度循环的关键节点之一。
它做的事情大意是:
- 先处理超时和运行时状态
- 尝试把上一轮 prefill batch 合并到当前 running batch
- 生成新的 prefill batch
- 如果有新 prefill batch,优先跑 prefill
- 否则继续 decode 当前 running batch
这一步能看出 SGLang 和 Ollama 的根本差别:
- Ollama 更像“先找到哪个模型实例来跑”
- SGLang 更像“现在应该让哪一批 token 先跑”
也就是说,SGLang 的调度粒度已经进入:
- batch 级
- prefill/decode 阶段级
- token 级
9. 第 7 跳:结果怎么回到 OpenAI streaming 响应
文件:
python/sglang/srt/entrypoints/openai/serving_chat.py
关键方法:
_handle_streaming_request()_generate_chat_stream()
这里的实现方式是:
StreamingResponse(...)包住_generate_chat_stream(...)_generate_chat_stream()内部异步遍历tokenizer_manager.generate_request(...)- 每拿到一段内部输出,就转换成 OpenAI 的 chunk
- 最终按 SSE
data: ...\n\n持续返回
这一层还会顺手处理:
- logprobs
- reasoning 内容分离
- tool call streaming
- usage 统计
- finish_reason
所以你可以把它理解成:
- 运行时流式输出 -> OpenAI SSE chunk 的最后一层包装器
10. 这条链路里对象是怎么变的
如果只看对象流转,这条链路可以压缩成:
ChatCompletionRequestGenerateReqInputTokenizedGenerateReqInputReq- 内部流式输出字典
content - OpenAI
ChatCompletionStreamResponse
这条对象变换链非常重要,因为它精确反映了 SGLang 的分层思想:
- 协议对象
- 通用运行时对象
- tokenized 对象
- 调度任务对象
- 协议响应对象
11. 学习这条链路时建议按什么顺序注释
接下来镜像注释建议按下面顺序推进:
python/sglang/srt/entrypoints/http_server.pypython/sglang/srt/entrypoints/openai/serving_chat.pypython/sglang/srt/managers/tokenizer_manager.pypython/sglang/srt/managers/scheduler.pypython/sglang/srt/managers/io_struct.py
这样做的原因是:
- 先把外层协议入口看清楚
- 再看 OpenAI chat 到内部请求对象的转换
- 再看运行时如何接住请求
- 再看调度器如何真正消费它
- 最后再回头把请求结构体补细
12. 一句话总结
/v1/chat/completions 在 SGLang 里不是“HTTP 直接调模型”,而是:
HTTP 路由 -> OpenAI 协议适配 -> 运行时请求对象 -> tokenizer manager -> scheduler -> 生成输出 -> OpenAI 流式封装
这正是 SGLang 作为“推理运行时平台”而不是“简单 API server”的体现。