从零开始:使用 mitmdump 抓包调试本地 LLM 服务完整指南

在开发和调试本地大语言模型(如 vLLM、Ollama)时,我们经常需要确认发送给模型的具体参数(Prompt、Temperature、Max Tokens 等)以及模型返回的原始流式数据mitmdumpmitmproxy 套件中的命令行工具,非常适合在服务器或无图形界面环境下进行流量分析。

本文将手把手教你从虚拟环境搭建到成功抓取并分析 LLM 流量的全过程。


第一步:准备 Python 虚拟环境

为了避免污染系统全局的 Python 包,强烈建议使用虚拟环境。

1. 创建虚拟环境

打开终端,进入你的工作目录,执行以下命令:

1
2
# 创建一个名为 venv 的虚拟环境
python3 -m venv venv

2. 激活虚拟环境

根据操作系统不同,执行不同的激活命令:

  • Linux / macOS:
    1
    source venv/bin/activate
  • Windows (PowerShell):
    1
    .\venv\Scripts\Activate.ps1
  • Windows (CMD):
    1
    venv\Scripts\activate.bat

激活后,你的终端提示符前应该会出现 (venv) 字样。


第二步:安装 mitmdump

在激活的虚拟环境中,使用 pip 安装 mitmproxy 包(它包含了 mitmdump):

1
pip install mitmproxy

安装完成后,验证是否成功:

1
mitmdump --version

如果输出了版本号,说明安装成功。


第三步:配置反向代理模式

我们要抓的是本机程序访问本机 LLM 服务的流量。
假设你的 LLM 服务(如 vLLM)运行在 http://127.0.0.1:8000

核心思路

  1. mitmdump 监听一个端口(例如 8080)。
  2. 配置 mitmdump反向代理模式 (Reverse Proxy),将所有收到的请求转发给真实的 LLM 服务 (8000)。
  3. 修改你的客户端代码,将请求地址从 8000 改为 8080

启动命令详解

执行以下命令启动抓包:

1
2
3
4
5
mitmdump \
--mode reverse:http://127.0.0.1:8000 \
--listen-port 8080 \
--set show_body=true \
-v

参数解释:

  • --mode reverse:http://127.0.0.1:8000: 最关键参数。开启反向代理模式,目标后端是本地 8000 端口的 LLM 服务。
  • --listen-port 8080: mitmdump 监听的端口。你的程序以后要连这个端口。
  • --set show_body=true: 强制显示请求和响应的主体内容(Body),否则默认可能只显示 Header。
  • -v: 详细模式 (Verbose),打印更多细节。

注意:如果是流式输出(Streaming),终端可能会刷屏。如果只想看请求参数,可以先不加 -v,或者配合后续的“保存文件法”。


第四步:修改客户端代码并发送请求

你需要将代码中连接 LLM 的地址改为 mitmdump 的监听地址 (http://127.0.0.1:8080)。

示例代码 (Python + OpenAI SDK)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from openai import OpenAI
import json

# 1. 初始化客户端,base_url 指向 mitmdump 的监听端口 (8080)
# 而不是真实的 vLLM 端口 (8000)
client = OpenAI(
api_key="na", # 本地通常不需要真实 key
base_url="http://127.0.0.1:8080/v1"
)

# 2. 定义请求参数
messages = [
{"role": "user", "content": "请简单介绍一下你自己"}
]

print("正在发送请求到 mitmdump (端口 8080)...")

try:
# 3. 发送请求 (开启流式 stream=True 以便测试复杂场景)
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=messages,
temperature=0.7,
max_tokens=200,
stream=True # 模拟流式输出
)

print("\n--- 模型回复开始 ---")
full_content = ""
for chunk in response:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_content += content
print("\n--- 模型回复结束 ---\n")

except Exception as e:
print(f"发生错误: {e}")

第五步:查看与分析流量

此时,回到运行 mitmdump 的终端窗口,你应该能看到类似的输出:

场景 A:直接在终端查看 (适合非流式或短响应)

如果配置正确,你会看到完整的 HTTP 报文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>> POST /v1/chat/completions HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/json
Content-Length: 145
...

{
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": "请简单介绍一下你自己"}],
"temperature": 0.7,
"max_tokens": 200,
"stream": true
}

<< HTTP/1.1 200 OK
Content-Type: text/event-stream
...

data: {"id":"...","choices":[{"delta":{"content":"你"},"index":0}]}
data: {"id":"...","choices":[{"delta":{"content":"好"},"index":0}]}
...
  • >> 下方:就是你发送的请求参数(重点检查 messagestemperature)。
  • << 下方:就是模型返回的流式数据

场景 B:响应太长刷屏怎么办?(推荐:保存到文件分析)

如果模型回复很长,终端会疯狂滚动,导致你看不清请求参数。最稳妥的方法是保存流量到文件,然后用脚本提取。

1. 重新启动 mitmdump 并保存文件

1
2
3
4
mitmdump \
--mode reverse:http://127.0.0.1:8000 \
--listen-port 8080 \
-w capture_flow.mitm
  • -w capture_flow.mitm: 将所有流量保存到当前目录下的 capture_flow.mitm 文件中。

2. 运行一次你的 Python 客户端代码。

3. 停止 mitmdump (Ctrl + C)。

4. 编写提取脚本查看参数

创建一个 analyze.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import json
from mitmproxy.io import io
from mitmproxy.http import HTTPFlow

filename = "capture_flow.mitm"

print(f"🔍 正在分析 {filename} ...\n")

with open(filename, "rb") as logfile:
reader = io.FlowReader(logfile)
for flow in reader.read():
if isinstance(flow, HTTPFlow):
# 提取请求体
if flow.request.content:
print("="*60)
print("📩 捕获到的请求参数 (Request Body):")
print("="*60)
try:
req_data = json.loads(flow.request.text)
# 美化打印 JSON
print(json.dumps(req_data, indent=2, ensure_ascii=False))
except:
print(flow.request.text)
print("\n")

# (可选) 提取响应体预览
if flow.response.content:
print("📤 响应体预览 (前 300 字符):")
print(flow.response.text[:300])
print("... (内容已截断)\n")

运行提取脚本:

1
python analyze.py

输出结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
============================================================
📩 捕获到的请求参数 (Request Body):
============================================================
{
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [
{
"role": "user",
"content": "请简单介绍一下你自己"
}
],
"temperature": 0.7,
"max_tokens": 200,
"stream": true
}

常见问题排查 (FAQ)

  1. 连接被拒绝 (Connection Refused)

    • 检查 mitmdump 是否正常启动且没有报错。
    • 确认你的代码中 base_url 确实改成了 http://127.0.0.1:8080
    • 确认真实的 LLM 服务在 8000 端口正常运行。
  2. 看不到 Body 内容

    • 确保启动了 --set show_body=true
    • 如果是流式响应,终端显示可能不完整,请务必使用 -w 保存文件 + 脚本分析 的方法。
  3. SSL/TLS 证书错误

    • 由于我们使用的是 http:// (非 https) 进行本地反向代理,通常不会遇到证书问题。
    • 如果你的 LLM 服务强制 HTTPS,配置会复杂一些(需要生成证书并信任),但在本地开发中,建议直接让 LLM 服务监听 HTTP 端口。

总结

通过 mitmdump 的反向代理模式,我们可以像“中间人”一样清晰地看到大模型交互的每一个细节。

  • 开发调试:确认 Prompt 是否被正确截断或修改。
  • 性能分析:查看首字延迟 (TTFT) 和 Token 生成速度。
  • 数据留存:保存 .mitm 文件作为测试数据集。

现在,你已经掌握了本地 LLM 流量分析的终极武器!🚀