vLLM Cline 연결 디버깅
vllm 서버에서 gpt-oss 120B 모델을 띄우고, 이 endpoint url에 VSCode Cline Extension을 연결해서 사용하고 있어.
네, 상황을 정리하면 이렇게 되네요.
-
vLLM 서버 에서 gpt-oss 120B 모델 을 띄움
-
그 모델의 endpoint URL 을 VSCode Cline Extension 에 연결해서 사용 중
이 구성이 동작하려면 크게 세 단계가 필요합니다.
1. vLLM 서버에서 gpt-oss 120B 모델 실행
- vLLM 설치
pip install vllm - 모델 실행 예시 (OpenAI API 호환 모드)
python -m vllm.entrypoints.openai.api_server \ --model gpt-oss-120b \ --tensor-parallel-size 4 \ --port 8000-
--model: Hugging Face 모델 경로 또는 로컬 경로 -
--tensor-parallel-size: GPU 개수 -
--port: API 서버 포트
-
이렇게 하면 http://<서버IP>:8000/v1 엔드포인트가 생깁니다.
2. VSCode Cline Extension 설정
Cline은 OpenAI API 형식을 따르기 때문에, vLLM 서버가 OpenAI 호환 모드로 띄워져 있어야 합니다.
-
VSCode → Cline Extension Settings → "API Provider"를
OpenAI로 선택 - API Base URL 을 vLLM 서버 주소로 설정:
arduino http://<서버IP>:8000/v1 - API Key 는 vLLM 서버에서 인증을 사용하지 않으면 아무 값이나 넣어도 됩니다.
(예:dummy-key)
3. 연결 확인
터미널에서 다음 명령으로 정상 응답을 확인:
curl http://<서버IP>:8000/v1/models
여기서 gpt-oss-120b가 뜨면 Cline에서도 바로 사용할 수 있습니다.
💡 팁
-
로컬 개발 환경에서 GPU 부족 시, vLLM 대신 Hugging Face Text Generation Inference(TGI)도 OpenAI API 호환 모드로 쓸 수 있습니다.
-
Cline이
gpt-oss 120B같이 초대형 모델을 호출하면 응답이 느릴 수 있으니, 프롬프트를 최대한 압축하거나 서버 쪽에서--max-num-batched-tokens를 조정하는 게 좋습니다.
원하시면 제가 vLLM gpt-oss 120B + Cline 연결 전체 예제 설정 파일 을 만들어서 드릴 수 있습니다.
이렇게 하면 서버 띄우고 바로 Cline에 붙일 수 있게 됩니다.
그런데 관련해서 디버깅을 좀 해보고 싶어. Cline이 서버로 보내는 쿼리와 받는 정보를 raw data에 가깝게 볼 수 있을까?
가능해요. “최대한 원본(raw)에 가깝게” 보려면 프록시를 사이에 둬서 요청/응답을 그대로 덤프 하는 게 가장 깔끔합니다. 아래를 순서대로 해보세요. (짧게 → 깊게)
0) 한줄 요약
-
권장: Cline ⇄ mitmproxy(reverse proxy) ⇄ vLLM
-
보너스: VSCode 로그(Extension Host/Window), 서버측(access log), 마지막 수단으로
ngrep/tcpdump
1) mitmproxy로 전체 HTTP 캡처(권장)
Cline이 호출하는 OpenAI 호환 REST 요청을 바이트 단위로 시/헤더/바디/스트리밍까지 볼 수 있습니다.
1-1. mitmproxy 설치
pip install mitmproxy
# 또는 macOS: brew install mitmproxy
1-2. vLLM 뒤에 리버스 프록시 세우기
vLLM이 http://<VLLM_HOST>:8000/v1 라고 가정:
mitmproxy -p 8080 --mode reverse:http://<VLLM_HOST>:8000 -w cline_flows.mitm
# GUI 원하면:
# mitmweb -p 8080 --mode reverse:http://<VLLM_HOST>:8000 -w cline_flows.mitm
1-3. Cline의 Base URL을 mitmproxy로 바꾸기
Cline의 API Base URL 을:
http://localhost:8080/v1
로 설정합니다. (API Key는 dummy 가능)
이제 mitm 화면에서 각 요청/응답을 클릭해 Headers / Content / Raw 탭으로 모두 확인 가능.
SSE(스트리밍)도 “Raw”에서 줄 단위로 보입니다.
1-4. 본문까지 파일로 떨구고 싶다면(옵션)
mitmdump -p 8080 --mode reverse:http://<VLLM_HOST>:8000 \
-s '"""
from mitmproxy import http
def response(flow: http.HTTPFlow):
with open("http_log.txt","ab") as f:
f.write((f"\\n=== {flow.request.method} {flow.request.pretty_url} ===\\n").encode())
f.write(flow.request.headers.bytes() + b"\\n\\n" + flow.request.raw_content + b"\\n")
f.write(b"--- RESPONSE ---\\n")
f.write(flow.response.headers.bytes() + b"\\n\\n" + (flow.response.raw_content or b"") + b"\\n")
"""'
주의: 본문에 토큰/프롬프트 등 민감정보가 포함됩니다. 로컬에서만 사용!
2) 초간단 “로깅 프록시” 직접 띄우기(대체안)
외부 도구 없이, 작은 Node 프록시 를 넣어도 됩니다.
# server.js
const http = require('http');
const { request: httpRequest } = require('http');
const TARGET = 'http://<VLLM_HOST>:8000';
http.createServer((clientReq, clientRes) => {
let body = [];
clientReq.on('data', chunk => body.push(chunk));
clientReq.on('end', () => {
body = Buffer.concat(body);
console.log('\n=== REQUEST ===');
console.log(clientReq.method, clientReq.url);
console.log(clientReq.headers);
console.log(body.toString());
const opts = {
method: clientReq.method,
headers: clientReq.headers,
path: clientReq.url,
host: new URL(TARGET).hostname,
port: new URL(TARGET).port,
};
const proxyReq = httpRequest(opts, proxyRes => {
let resBody = [];
proxyRes.on('data', chunk => {
resBody.push(chunk);
clientRes.write(chunk); // 스트리밍 패스스루
});
proxyRes.on('end', () => {
resBody = Buffer.concat(resBody);
console.log('\n--- RESPONSE ---');
console.log(proxyRes.statusCode, proxyRes.headers);
console.log(resBody.toString());
clientRes.end();
});
});
proxyReq.on('error', e => {
console.error('proxy error', e);
clientRes.statusCode = 502;
clientRes.end('proxy error');
});
proxyReq.write(body);
proxyReq.end();
});
}).listen(8080, () => console.log('logging proxy on :8080'));
node server.js
# 그리고 Cline Base URL을 http://localhost:8080/v1 로
3) VSCode 쪽에서 볼 수 있는 로그
-
Output 패널
-
VSCode: View → Output → 드롭다운에서
- “Log (Extension Host)” , “Log (Window)” , 혹시 있다면 “Cline” 채널 확인
-
-
로그 레벨 올리기
- Settings에서 “Log Level” → “Trace”로 설정
-
Developer Tools
-
Help → Toggle Developer Tools → Console 탭
-
일부 네트워크/콘솔 로그가 찍힙니다(확장 작동 방식에 따라 차이)
-
확장마다 남기는 로그 범위가 달라 실제 요청/응답 바디 전체 는 안 나올 수 있어요. 그래서 위의 프록시 접근이 가장 확실 합니다.
4) 서버(vLLM) 측 로그를 좀 더 보기
-
vLLM OpenAI 서버는 Uvicorn 위에서 돌아가므로 access log 는 기본적으로 나옵니다.
-
더 자세히 보려면(바디는 기본 미포함):
python -m vllm.entrypoints.openai.api_server \ --model gpt-oss-120b \ --port 8000 \ --log-level debug
이건 라우트/상태 정도만 보이고, 요청/응답 바디는 노출되지 않습니다.
원본 바디가 필요하면 1번(프록시)로 가는 게 정석.
5) 네트워크 레벨에서 “그냥 까보기”(HTTP일 때만)
서버와 Cline 사이가 평문 HTTP 라면, 패킷 레벨로도 확인 가능합니다.
ngrep(가독성 좋음)sudo ngrep -d any -W byline port 8000tcpdump(바이너리/헤더까지)sudo tcpdump -A -s0 'tcp port 8000'
HTTPS라면 내용은 해독이 안 되니 프록시가 필요합니다.
6) 재현용: curl로 같은 요청 만들어보기
Cline이 보내는 페이로드를 캡처했으면, 동일한 요청 을 curl로 반복 재현하면서 비교 디버깅하기 좋습니다.
curl -v http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dummy" \
-d '{
"model": "gpt-oss-120b",
"messages": [{"role":"user","content":"hello"}],
"stream": true
}'
-v/--trace-ascii dump.txt로 wire-level 로그 확보 가능
어떤 방법이 제일 나을까?
-
가장 확실하고 빠른 해법은 1) mitmproxy reverse 입니다. UI로 한눈에 보이고, 파일로도 저장 가능.
-
VSCode 로그는 “보조”.
-
서버 로그/패킷 스니핑은 상황 따라 도움.
원하시면, 현재 환경(IP/포트/HTTPS 여부) 알려주면 제가 딱 맞춘 명령어와 Cline 설정 스크린샷 가이드 까지 만들어드릴게요.
API 키 입력이 필요하면?
Cline이 API 키를 반드시 요구하는 경우라도, vLLM 서버가 인증을 쓰지 않는다면 사실상 아무 문자열이나 넣으면 됩니다.
왜냐면 vLLM의 OpenAI 호환 API는 기본적으로 API 키 검증을 하지 않기 때문이에요.
1. 인증 없는 vLLM 서버일 때
-
Cline의 API Key 입력칸에
dummy또는test처럼 아무 값 입력 -
mitmproxy/프록시/서버 모두 그대로 통과합니다.
-
요청 헤더 예시:
Authorization: Bearer dummy
2. vLLM에서 API 키 검증을 켠 경우
만약 --api-key 같은 인증 옵션을 활성화했다면(또는 API Gateway 뒤에 둔 경우):
- 서버에서 설정한 키 를 기억하거나 환경변수로 확인
export VLLM_API_KEY="my-secret-key" -
Cline API Key 입력란에 해당 키를 입력
- 프록시나 mitmproxy로 캡처 시에도 이 키가 그대로 Authorization 헤더에 뜹니다.
3. 디버깅 프록시에서 키를 안전하게 처리하는 팁
- 요청 로깅 시 Authorization 헤더를 마스킹 하면 좋습니다.
# mitmproxy 스크립트 예시 from mitmproxy import http def request(flow: http.HTTPFlow): if "authorization" in flow.request.headers: flow.request.headers["authorization"] = "Bearer ***MASKED***" - 내부 개발/로컬 환경이면 그냥 그대로 봐도 무방하지만, 키가 외부로 유출되면 안 됩니다.
📌 결론
-
인증 없으면 Cline에 임의 문자열 넣으면 끝.
-
인증 있으면 vLLM 서버에 설정한 키를 넣어야 함.
-
디버깅 시엔 프록시에서 키를 가려주는 습관을 들이면 안전합니다.
필요하다면 제가 API 키 유무별 Cline–vLLM–mitmproxy 연결 전체 그림 을 그려서, 어떤 값이 어디로 흘러가는지 보여드릴 수 있습니다.
그렇게 하면 디버깅 흐름이 한눈에 보입니다.
했는데 API Streaming Failed Unexpected token 173781 while expecting start token 200006 에러가 떠 cline에서
이 증상, 거의 100% 스트리밍 파서/포맷 불일치 예요.
특히 메시지에 나온 start token 200006는 OpenAI o-series(=gpt-4o 계열) 토크나이저의 특수 토큰 ID라서, Cline이 o-series/Harmony 포맷을 기대 하는데 vLLM의 일반 Chat Completions 스트림 을 받는 경우에 잘 터집니다. docs.cline.botVLLM Docs
아래 순서대로 체크해보세요. (위에서부터 한 단계씩)
1) Cline가 Responses API 가 아니라 Chat Completions 를 때리게 만들기
-
Cline 설정 → API Provider: OpenAI Compatible
-
Base URL :
http://<서버IP>:8000/v1 -
Model ID : vLLM에 띄운 정확한 이름(예:
gpt-oss-120b) -
가능하면 Responses API(“/v1/responses”) 옵션/모드 꺼두기 → vLLM은 공식적으로 /v1/chat/completions 스트림을 지원합니다. VLLM Docs
왜? vLLM의 OpenAI 호환 서버는 Completions/Chat/Embeddings까지만 구현되어 있고, OpenAI의 최신 Responses/Harmony(o-series) 이벤트 스트림과는 필드/이벤트명이 달라서 Cline 파서가 못 알아듣습니다. VLLM Docs
2) Cline에서 “o-series/Reasoning/Computer Use/병렬 툴콜” 꺼두기
-
모델을 o1/o3/gpt-4o 류가 아닌 일반 채팅형 으로 고정
-
Tool/Computer Use 기능을 일단 OFF (vLLM의 함수/툴콜 구현과 Cline의 기대치가 어긋날 수 있음)
3) 스트리밍 자체가 문제인지 분리
-
Cline 설정에서 Stream 비활성화 가 가능하면 잠시 꺼서 테스트
-
혹은 동일 요청을 curl로 재현 해 보세요:
# 스트림 OFF로 테스트(파서 우회) curl http://<서버IP>:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer dummy" \ -d '{ "model":"gpt-oss-120b", "messages":[{"role":"user","content":"ping"}], "stream": false }'
이건 잘 되는데 Cline에서만 터지면 Cline의 SSE 파서/옵션 불일치 로 확정.
4) mitmproxy로 실제 스트림 모양 확인
- Cline ↔ vLLM 사이에 mitmproxy(reverse)를 두고 SSE 라인 을 봅니다.
data: { ... }가 JSON 청크로 오다가 마지막에data: [DONE]으로 끝나야 Cline이 행복합니다. 만약 Responses API 이벤트명(예:event: response.delta) 비슷한 게 보이면 라우팅이 잘못된 겁니다. VLLM Docs
5) vLLM 서버 쪽 권장 실행 플래그
python -m vllm.entrypoints.openai.api_server \
--model gpt-oss-120b \
--host 0.0.0.0 --port 8000 \
--tensor-parallel-size <GPU수> \
--enable-log-requests #(문제시 요청 로깅 보조)
- 반드시 Cline이 /v1/chat/completions 로 치는지 확인하세요. (mitmproxy/curl)
6) 흔한 함정 정리
-
Base URL 끝에 /v1 빠짐 → 404/파서오류 유발
-
Model ID 오타 → 일부 게이트웨이는 다른 엔드포인트로 우회 응답
-
JSON 모드/Response Format 강제(예: json_schema) → 일부 모델에서 스트림 형식이 달라져 파서 에러
-
HTTP 압축/프록시 변환 이 SSE 줄바꿈을 깨뜨리는 경우
왜 200006이 보였나?
o-series(Harmony) 포맷에서 쓰는 특수 토큰 ID들이 있고, 글 중에도 200006 같은 ID가 언급됩니다. Cline이 그 포맷(=특정 토큰 경계/이벤트)을 기대 했는데, vLLM이 일반 Chat 스트림 을 보내니 “기대하던 시작 토큰이 아니다” 류의 에러가 난 것으로 추정됩니다. Simon Willison’s Weblog
필요하면 지금 Cline 설정 스크린샷(모델/엔드포인트/토글 항목)과 mitmproxy 캡처 한두 건만 올려 주세요.
그대로 보고 “어느 지점에서 Responses↔Chat이 엇갈리는지” 정확히 짚어드릴게요.
근데 mitmproxy를 안쓰면 저 에러가 안나
오—그럼 원인 범위가 mitmproxy 쪽 으로 꽤 좁혀졌어요.
SSE(서버센트 이벤트) 스트림을 mitmproxy가 버퍼링/재인코딩/HTTP2 변환 하면서 Cline의 파서가 기대하는 포맷이 깨지는 전형적인 케이스 같습니다.
아래 순서대로 “한 가지씩” 바꿔 보세요. (위에서부터 효과 큰 것 ▶ 세부)
1) mitmproxy 실행 옵션을 스트리밍 친화적으로 바꾸기
SSE는 라인 경계(줄바꿈)와 chunking이 아주 민감해요. 다음 옵션이 핵심입니다.
mitmproxy \
--mode reverse:http://<VLLM_HOST>:8000 \
-p 8080 \
--set http2=false \
--set connection_strategy=lazy \
--set stream_large_bodies=1
-
http2=false: 업스트림/다운스트림에서 HTTP/2로 변환되는 걸 막아서 SSE 라인 경계 깨짐 을 예방 -
connection_strategy=lazy: 불필요한 선연결/재사용 줄임 -
stream_large_bodies=1: 응답을 버퍼링하지 말고 바로바로 흘려보냄 (SSE 필수)
Cline Base URL은
http://localhost:8080/v1로 유지
2) “바디 찍는” mitm 스크립트는 일단 끄기
제가 전에 드린 예시처럼 flow.response.raw_content를 파일로 쓰면 mitm이 본문을 수집하려고 버퍼링 할 수 있어요.
스크립트 없이 UI에서 Raw 탭으로 먼저 확인해 보세요.
(로그를 꼭 남겨야 한다면, responseheaders 이벤트에서 헤더만 찍고 바디는 건드리지 않는 방식으로…)
# headers만 로깅(버퍼링 방지)
from mitmproxy import http
def responseheaders(flow: http.HTTPFlow):
print("\n=== RESPONSE HEADERS ===")
print(flow.response.status_code, dict(flow.response.headers))
# 스트리밍 강제
flow.response.stream = True
3) gzip/인코딩 간섭 줄이기
가능하면 Cline 쪽에서 Accept-Encoding: identity 를 강제하거나, vLLM이 text/event-stream에 대해 압축을 안 하도록(기본적으로는 안 함) 둡니다.
압축이 끼면 중간 프록시가 재압축/해제 하면서 줄 경계가 어긋날 수 있어요.
4) 프록시 경로/중복 prefix 확인
reverse 모드에서 Base URL이/v1 인데, upstream에도 /v1가 붙은 주소로 중복되면 라우팅 꼬입니다.
-
mitmproxy:
--mode reverse:http://<VLLM_HOST>:8000 -
Cline Base URL:
http://localhost:8080/v1 -
Upstream vLLM 라우트는
/v1/chat/completions로 딱 한 번만 들어가야 합니다.
5) 재현 테스트 (직접 curl로 Cline 대신 쏘기)
mitm를 끼웠을 때만 깨지는지, 그리고 옵션 수정 후 복구됐는지 빠르게 확인:
# Stream 켠 채로 mitm 경유
curl -N -v http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dummy" \
-d '{ "model":"gpt-oss-120b",
"messages":[{"role":"user","content":"ping"}],
"stream": true }'
정상이라면 이런 패턴으로 흘러와야 합니다:
data: {"id":"...","object":"chat.completion.chunk", ...}
data: {"id":"...","object":"chat.completion.chunk", ...}
...
data: [DONE]
6) 그래도 안 되면: 대안 2가지
-
HTTP/1.1 고정 프록시(tinyproxy/nginx/HAProxy)로 단순 패스스루만 하게 하기
- nginx 예:
proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off;
- nginx 예:
-
ngrep/tcpdump 로 패킷 레벨 캡처(프록시 없이도 raw 확인 가능)
7) 체크리스트(한 번에 훑기)
-
mitm
http2=false,stream_large_bodies=1적용 -
커스텀 mitm 스크립트에서 본문 접근 제거 (헤더만 로깅)
-
Base URL 경로 중복 없음(
/v1한 번만) -
SSE 응답 헤더 확인:
Content-Type: text/event-stream,Cache-Control: no-cache -
gzip/deflate 비활성화(가능하다면)
-
Cline이 /v1/responses 가 아닌 /v1/chat/completions 로 호출 중
원하시면 지금 쓰신 mitm 실행 커맨드랑(옵션 포함), Cline Base URL/모델 설정을 알려주세요.
그대로 맞춰서 오류 재현 → 최소 수정 세트 를 딱 뽑아드릴게요.
http2=false 옵션 하나만 추가하니 잘 돼네
아, 그럼 원인이 거의 확정이네요 — mitmproxy가 기본으로 HTTP/2로 업스트림 연결 을 시도하면서 SSE(서버센트 이벤트) 스트림의 줄 경계나 전송 방식이 미묘하게 변형돼서, Cline 파서가 깨졌던 겁니다.
왜 http2=false로 해결됐나
-
SSE는 HTTP/1.1의 chunked transfer 에 의존하는 경우가 많습니다.
-
mitmproxy가 HTTP/2로 업스트림을 붙으면, 내부에서 프레임 단위로 전송 → 중간에 라인 경계(
\n\n)가 바뀌거나 버퍼링 타이밍이 달라짐. -
특히 Cline 같이 실시간 토큰 단위 파서를 쓰는 쪽은 이 변화에 민감해서, "Unexpected token" 같은 오류를 뿜습니다.
-
--set http2=false는 mitmproxy가 업/다운 스트림 모두 HTTP/1.1로 고정 하게 해서 원본 chunking/SSE 형식을 보존하게 만듭니다.
안전하게 쓰는 mitmproxy SSE 프록시 실행 예시
mitmproxy \
--mode reverse:http://<VLLM_HOST>:8000 \
-p 8080 \
--set http2=false \
--set connection_strategy=lazy \
--set stream_large_bodies=1
-
connection_strategy=lazy→ 불필요한 선연결 방지 -
stream_large_bodies=1→ 응답 버퍼링 없이 바로 흘려보냄
앞으로 비슷한 상황 방지 팁
-
SSE 프록시에는 HTTP/2 강제 비활성
-
본문 로깅이 필요하면 stream 모드에서 헤더만 찍고 바디는 건드리지 않기
-
gzip/deflate 같은 압축은 가급적 꺼두기 → 중간 해제 시 줄 경계 깨짐 방지
-
프록시로 테스트한 curl 요청이 정상 스트리밍이면, Cline에서도 99% 잘 동작
이제 http2=false로 고정한 상태에서 디버깅 로그 수집도 문제없이 가능할 거예요.
원하시면 제가 Cline↔mitmproxy↔vLLM 흐름도 그려서, 데이터가 어떻게 흘러가고 어디서 줄 경계가 깨지는지 시각적으로 보여드릴 수도 있습니다.