courses
대시보드에 버그가 실려 배포되면 디버깅 루프는 늘 같습니다. 화면을 보고, 원인 파일을 찾고, 수정하고, 테스트를 다시 돌리고, 페이지를 새로고침한 뒤 다시 확인합니다. 단조롭고 지루하며, 증거의 절반은 스택 트레이스가 아니라 스크린샷에 남습니다.
이 실험은 DeepSeek가 DeepSeek V4.1 Flash를 출시한 직후 시작했습니다. 새 아키텍처 계열 중 가장 작은 모델로, 이미지 입력을 받을 수 있습니다. 깨진 웹앱을 검사하고, 코드를 패치하며, 완료 시점을 스스로 판단할 수 있는지 알고 싶었습니다.
이 튜토리얼은 하나의 프로젝트에 초점을 맞춥니다. 에이전트가 DeepSeek의 Responses API 포맷 구현을 통해 찾아 고치도록 설계된 세 가지 버그가 있는 Nimbus Analytics Launch Metrics라는 작은 Flask 대시보드입니다. 기록된 실행은 또한 에이전트 도구의 빈틈 하나를 드러냅니다.
다음 내용을 다룹니다.
-
Responses API를 통해 DeepSeek V4.1 Flash에 첫 요청 보내기
-
모델에 기준 스크린샷을 제공하고, 도구 출력으로 최신 Playwright 스크린샷을 전달하기
-
에이전트에 파일 목록 조회, 파일 읽기, pytest 실행, 그리고
apply_patch한 번으로 여러 파일에 패치 적용하기 -
API가 상태 비저장(stateless)인 만큼 대화를 저장하고 재전송하기
-
구조화된 JSON 수리 보고서 반환하기
-
캐시된 입력, 추론, 출력 토큰에서 비용 계산하기
요약
DeepSeek V4.1 Flash의 Responses API는 상태 비저장이므로, Python 코드는 대화를 저장해 매 턴마다 다시 보냅니다. 같은 루프에서 기준 이미지와 도구 스크린샷을 비전으로 사용하고, 여러 파일을 검사하는 추론 모드, 편집에는 apply_patch를 씁니다. 이번 실행에서 얻은 네 가지 상세 결과는 다음 버전을 설계하는 방식을 바꾸었습니다.
- 하나의 패치로 세 버그를 한 번에 해결: 단일 apply_patch 호출이 CSS, JavaScript, Python 파일을 차례로 수정했으며, 총 14턴 예산 안에서 끝났습니다.
- 컨텍스트 캐싱이 대부분의 입력 토큰을 커버: 156,724개 입력 토큰 중 137,088개가 캐시되어 87% 히트율을 보였습니다.
- 진단이 정확해도 완전한 검증을 보장하진 않음: 에이전트는 오래된 Flask 프로세스를 올바르게 지적했지만 재시작 도구가 없어 시각적 일치를 스스로 확인하지는 못했습니다.
- 측정된 API 비용은 약 $0.0103: 수리 루프 14턴과 최종 JSON 보고서 요청을 포함한 금액입니다.
이 수치는 하나의 작은 대시보드에서 단 한 번 실행한 결과일 뿐 벤치마크가 아닙니다. 턴 수, 캐시 히트율, 비용은 더 큰 앱이나 다른 버그 세트에서는 달라질 수 있습니다.
DeepSeek V4.1 Flash란?
DeepSeek는 모델 ID deepseek-flash로 API를 통해 V4.1 Flash를 제공합니다. 이미지 입력을 받고, 추론 모드와 비추론 모드를 지원하며, 100만 토큰 컨텍스트 윈도우를 갖고, Chat Completions와 Responses API를 통해 최대 38.4만 토큰까지 반환할 수 있습니다.
저희의 DeepSeek V4.1 Flash 개요는 출시, 아키텍처, 벤치마크를 다룹니다.
DeepSeek V4.1 Flash는 어떻게 동작하나요?
DeepSeek는 V4.1 Flash를 552B 파라미터 MoE 백본으로 설명하지만, Hugging Face는 공개 체크포인트에 대해 763B 파라미터라고 보고합니다. 이 차이는 주로 196B 파라미터의 Engram 조건부 메모리와 비전 인코더, 프로젝터 때문입니다. 이들은 체크포인트에 포함되지만 MoE 백본 바깥에 위치한 구성 요소입니다.
Causal Encoder-Decoder 설계는 캐시된 인코더 상태를 재사용하며, 입력 처리 시 토큰당 8B, 출력 시 16B의 활성 파라미터를 사용합니다.
DeepSeek V4.1 Flash의 새로워진 점은?
V4.1 Flash는 새로운 V4.1 아키텍처 계열의 첫 모델로, 이미지 이해가 네이티브로 내장되어 있습니다. 비전 및 텍스트 임베딩을 사전학습 시작부터 공동 학습하며, 실험적 모델이던 V4-Flash-Vision-Exp처럼 사후에 추가하지 않습니다.
Responses API는 V4.1 Flash보다 먼저 있었고, DeepSeek는 이전 V4 롤아웃 중에 네이티브 지원을 추가했습니다. 사용 중단된 모델 이름인 deepseek-v4-flash와 deepseek-v4-flash-vision-exp는 이제 V4.1 Flash로 라우팅됩니다.
DeepSeek V4.1 Flash 비용은 얼마인가요?
DeepSeek의 요금은 피크 시간대 기준이며, 비피크 요금은 피크의 50%로 책정됩니다. 제가 에이전트를 실행했을 때, 캐시된 입력은 비피크 100만 토큰당 $0.003, 피크 $0.006, 캐시되지 않은 입력은 비피크 $0.15, 피크 $0.30, 출력은 비피크 $0.60, 피크 $1.20이었습니다. 자세한 내용은 DeepSeek 요금 페이지를 참고하세요.
피크 시간은 협정 세계시(UTC) 기준 월요일부터 금요일까지 01:00–04:00, 06:00–10:00이며, 중국의 공휴일은 제외됩니다. 그 외 모든 시간은 비피크이며, 중국의 공휴일은 전부 비피크입니다.
무엇을 만들까요: Launch Metrics 시각적 수리 에이전트
Nimbus Analytics Launch Metrics는 방문자 수, 가입 수, 전환율, 매출, 일일 가입 수를 보여주는 Flask 대시보드입니다. 저는 세 개의 버그를 세 파일에 흩어두었고, 에이전트에게 무엇인지 알리지 않았습니다. 코드와 깨진 대시보드는 이 GitHub 저장소에 있습니다.

기준 디자인과 나란히 놓인 깨진 대시보드. 이미지: 필자.
세 가지 버그는 서로 다른 증거를 필요로 합니다. 하나는 스크린샷에서 드러나고, 하나는 브라우저 동작에 영향을 미치며, 하나는 pytest에서 실패합니다. 에이전트는 버그 목록을 받지 않습니다.
에이전트에 넘기기 전에 저는 "수정 완료"의 기준을 정의합니다. pytest 스위트가 통과해야 하고, 새로운 스크린샷이 기준 이미지와 시각적으로 일치해야 합니다. 모델의 판단만으로는 부족하므로, 러너가 두 가지 증거를 모두 확인합니다.
수리 루프의 작동 방식
루프는 모델 요청과 로컬 도구 실행을 번갈아 수행합니다. V4.1 Flash는 추론, 메시지 또는 도구 호출을 반환하고, Python은 요청된 도구를 실행해 결과를 히스토리에 추가합니다. 모델이 추가 도구 호출 없이 응답하거나 14턴 제한에 도달하면 루프가 종료됩니다.

모델, 도구, 브라우저를 잇는 수리 루프. 이미지: 필자.
DeepSeek V4.1 Flash API 설정 방법
Python 3.10 이상과 크레딧이 있는 DeepSeek API 키가 필요합니다. DeepSeek API는 OpenAI 요청 형식을 따르므로, 이 프로젝트는 openai Python 패키지를 사용하고 base_url 은 DeepSeek로 설정합니다.
가상 환경을 만들고 프로젝트에 필요한 패키지를 설치하세요.
python3 -m venv .venv
source .venv/bin/activate
pip install openai flask playwright pytest python-dotenv requests streamlit
playwright install chromium
저는 openai 3.14.1, flask 3.1.3, playwright 1.63.0에서 테스트했습니다. 키는 프로젝트 루트의 .env 파일에 DEEPSEEK_API_KEY=sk-... 형태로 저장하고, python-dotenv로 로드하세요. 이미 Responses API와 함께 키가 작동한다면 다음 코드 블록은 건너뛰세요. 그렇지 않다면 이 요청이 키와 베이스 URL을 확인합니다.
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")
response = client.responses.create(model="deepseek-flash", input="Say hi in five words.")
print(response.output_text)
짧은 인사말이 출력되면 키와 베이스 URL이 올바르게 설정된 것입니다.
1단계: 모델에 "수정 완료"의 모습 보여주기
에이전트의 첫 입력에는 기준 스크린샷, 짧은 작업 설명, 실시간 URL이 포함됩니다. 사용자 메시지로 보내는 이미지는 이 한 장뿐입니다. 이후 스크린샷은 모두 도구를 통해 전달됩니다.
러너는 매 요청마다 기준 이미지를 base64 데이터 URL로 보냅니다. 이미지를 재사용할 때는 Files API를 권장합니다. file_id를 사용하면 같은 이미지 데이터를 매번 보낼 필요가 없습니다.
변경을 허용하기 전 초기 반응 받기
첨부 이미지에서 모델에게 먼저 무엇을 확인할지 물었지만, 도구는 제공하지 않았습니다. 이렇게 하면 모델이 아무것도 수정하기 전에 계획을 살필 수 있습니다. 응답은 프로젝트 파일 목록 조회, CSS 변수 추적, 스크린샷 촬영을 제안했습니다. 저는 reasoning: {"effort": "high"}, DeepSeek의 기본 추론 수준을 사용했습니다.
2단계: 에이전트에 사용할 도구 제공하기
에이전트는 네 가지 함수 도구와 하나의 커스텀 도구를 받습니다.
-
list_files와read_file은 프로젝트를 검사하며, 둘 다dashboard/와tests/로 제한됩니다. -
run_tests는 pytest를 실행합니다. -
capture_dashboard_screenshot는 Playwright를 통해 헤드리스 Chromium을 실행합니다.
커스텀 도구는 apply_patch이며, {"type": "custom", "name": "apply_patch"} 로 선언되며 "Codex 호환성"을 위해 허용됩니다. 다른 커스텀 도구 이름은 400 에러를 반환하고, 웹 검색이나 컴퓨터 사용 같은 내장 타입은 조용히 무시됩니다.
함수 인수는 JSON 텍스트로 전달되며 Python이 실행하기 전 검사됩니다. apply_patch는 커스텀 도구 입력으로 도착하므로, 코드는 이를 별도로 처리하고 파일을 쓰기 전에 패치를 점검합니다. 도구 에러는 루프를 중단하지 않고 모델에 반환됩니다.
Playwright 스크린샷을 도구 출력으로 되돌려 보내기
capture_dashboard_screenshot가 실행되면, 결과는 디스크에 저장되지 않습니다. Python은 이를 function_call_output 내부의 input_image 파트로 반환합니다. 그러면 DeepSeek는 스크린샷을 텍스트 설명이 아닌 이미지로 읽습니다.
history.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": [{"type": "input_image", "image_url": f"data:image/png;base64,{png_b64}"}],
})
에이전트는 CSS를 패치하고, 다시 스크린샷을 찍어 숫자가 읽기 쉬운지 확인할 수 있습니다.
3단계: 에이전트 루프를 구성하고 히스토리를 직접 관리하기
히스토리는 Python 리스트에 저장됩니다. API가 previous_response_id나 서버 측 대화를 지원하지 않기 때문입니다. 추론 모드는 이전 도구 턴의 모든 추론 항목도 필요로 합니다.
중요: 같은 턴 내 두 호출 사이에 도구 출력을 삽입하면 다음 요청에서 400 에러가 반환됩니다. 항상 response.output의 모든 항목을 순서대로 추가한 뒤, 도구를 실행하고 그 결과를 추가하세요.
러너는 에이전트를 14턴과 dashboard/, tests/ 디렉터리로 제한합니다. 셸 접근은 제공하지 않으며, 도구 인수를 점검하고, 검증에는 pytest를 사용합니다.
DeepSeek V4.1 Flash는 구조화 출력(Structured Output)을 지원하나요?
예. Responses API를 통해 DeepSeek V4.1 Flash는 text.format을 사용하여 JSON 스키마를 받을 수 있습니다. Chat Completions의 response_format 은 JSON 모드를 지원하지만 스키마는 지원하지 않습니다. 루프가 멈춘 뒤 최종 요청에서 버그, 수정 사항, 테스트 결과, 스크린샷 결과, 검증 방법을 기록합니다.
프로젝트에는 Streamlit 앱도 포함되어 있으며, 파일은 app_streamlit.py입니다. 동일한 에이전트가 stream=True로 제너레이터로 실행되어, 페이지는 도착 즉시 추론 텍스트와 도구 호출을 표시합니다. 사이드바에서 추론 노력도와 이미지 상세 수준을 변경할 수 있습니다.
Streamlit UI가 에이전트 실행을 스트리밍합니다. 영상: 필자.
4단계: 시각적 버그 수정 에이전트 실행하기
패치 하나로 끝난 듯 보였지만, 라이브 페이지의 반응은 달랐습니다.
버그 찾기와 수정
에이전트는 처음 두 턴을 아무것도 건드리지 않고 관찰에 썼습니다. 1턴은 파일을 나열하고 기준 스크린샷을 찍었고, 2턴은 app.py, index.html, style.css, 테스트 파일을 읽었습니다.
3턴에서 pytest를 실행했고, 4턴에서 하나의 패치를 적용해 전환율 공식 수정, 지표 색상 변경, JavaScript 조회 대상이 캔버스 ID와 일치하도록 수정했습니다.
- conversion_rate = data["conversions"] / data["signups"] * 100
+ conversion_rate = data["conversions"] / data["total_visitors"] * 100
5턴에서 테스트를 다시 돌리자 전부 5개가 통과했습니다. 하지만 여기서부터 깔끔함이 흐트러졌습니다. 이후 스크린샷마다 여전히 전환율 15%와 빈 차트가 보였습니다.
시스템 정체(staleness) 파악과 해결
에이전트는 디스크의 파일에 수정이 반영된 것을 확인하고 스크린샷을 재시도했으며, 서버가 변경된 Python과 템플릿 파일을 로딩하는지 탐색했습니다. 임시 신선도 체크 두 가지 역시 라이브 페이지에 나타나지 않았습니다.
14턴에 이르러 루프는 예산에 도달했고 원인을 파악했습니다. run_tests는 디스크의 코드를 확인하는 반면, 스크린샷은 상태가 오래된 실행 중인 프로세스를 확인한다는 점입니다. Flask가 debug=False로 시작되어 리로더가 변경된 Python 모듈을 불러오지 않았고, 템플릿 자동 리로드도 활성화되지 않았습니다.
CSS 변경은 보였지만, Python에서 계산한 값과 템플릿 기반 차트는 여전히 오래된 상태였습니다. pytest는 디스크의 app.py 를 임포트했기에, 테스트 통과가 곧바로 최신 페이지를 보장하진 못했습니다.
Flask를 재시작하자 대시보드가 기준 이미지와 일치했습니다. 빠져 있던 것은 또 다른 코드 패치가 아니라 restart_server 도구였습니다.

재시작 후 패치된 대시보드 변경이 보입니다. 이미지: 필자.
에이전트가 대시보드를 고쳤나요?
네, 에이전트는 디스크 상의 대시보드를 수정했습니다. 문제가 있던 세 파일만 변경했으며, pytest는 4개 실패에서 5개 전부 통과로 전환되었습니다. Flask 재시작 이후 라이브 페이지에도 모든 수정이 반영되었습니다.
5단계: 사용량, 캐싱, 비용 측정
에이전트가 히스토리를 재전송하기 때문에, 이후 요청은 이전 턴의 입력 상당 부분을 반복합니다. DeepSeek는 이 반복 접두사를 자동 캐시와 대조합니다. 캐시는 최선 노력(best-effort) 기반으로 동작하므로, 여기 수치는 이번 실행에만 해당합니다.
14번의 수리 턴과 최종 JSON 보고서 요청을 통틀어, API는 156,724개의 입력 토큰을 보고했으며 이 중 137,088개가 캐시(87% 히트율)였습니다. 출력은 총 11,497토큰으로, 그중 9,362토큰이 추론 토큰이었습니다. 비피크 시간대 실행이어서 15건 전체 요청 비용은 약 $0.0103에 그쳤습니다.

추론 출력이 가장 큰 비용 범주입니다. 이미지: 필자.
코드베이스가 더 크거나, 스크린샷이 더 많거나, 캐시 히트율이 낮다면 토큰 수와 비용은 달라질 수 있습니다.
알아둘 DeepSeek V4.1 Flash API 제한 사항
데모를 넘어 이 러너를 확장하기 전에 세 가지 API 제한이 중요합니다.
-
백그라운드 응답을 지원하지 않아, 긴 턴은 완료될 때까지 블로킹됩니다.
-
parallel_tool_calls와max_tool_calls는 무시되며, 병렬 도구 호출은 계속 활성화됩니다. -
자동 잘라내기(truncation)를 지원하지 않아, 컨텍스트 한도를 초과하는 요청은 400 에러를 반환합니다.
DeepSeek V4.1 Flash 에이전트 배포 체크리스트
이 패턴을 라이브 서비스에 사용할 때는, 제어 로직을 모델 지시문이 아니라 애플리케이션 코드에 두세요.
- 턴 수와 비용 한도를 강제하고, 한도 도달 시 알림 보내기
- 파일 접근을 제한하고 모든 도구 인수를 점검하기
- 서비스 재시작 및 확인 도구를 제공해, 검증이 최신 코드를 대상으로 수행되도록 하기
- 토큰 사용량, 도구 호출, 테스트 결과, 최종 상태를 로깅하기
apply_patch와 일반 함수 도구는 언제 써야 하나요?
apply_patch는 하나의 변경으로 여러 파일을 동시에 갱신해야 할 때 사용하세요. 이 경우처럼요. 패치 후에는 테스트를 실행하세요. 한 번의 잘못된 호출로 여러 파일이 손상될 수 있습니다.
read_file과 write_file 은 각 수정에 별도 점검이나 승인이 필요할 때 사용하세요. 턴 수는 늘지만, 잘못된 수정의 영향이 한 파일로 제한됩니다.
마무리 생각
시각적 수리 루프는 하나의 패치로 세 가지 버그를 모두 수정했지만, 완전히 깔끔한 성공은 아니었습니다. pytest는 통과했지만 Flask는 여전히 오래된 Python과 템플릿 출력을 제공했기 때문에, 서버를 제가 재시작하기 전까지 에이전트는 최종 페이지를 확인할 수 없었습니다.
더 큰 앱을 테스트하기 전에 restart_server 도구와 픽셀 비교를 추가하겠습니다. 파일 경계와 턴 제한은 유지하되, pytest와 스크린샷 비교를 별개 체크로 다루겠습니다. 하나를 통과했다고 해서 다른 하나까지 대체할 수는 없습니다.
FAQs
DeepSeek V4.1 Flash는 URL에서 이미지를 읽을 수 있나요?
가능합니다. Responses API는 공개 이미지 URL, base64 데이터 URL, Files API의 file_id를 받을 수 있습니다.
에이전트의 패치로 테스트 실패가 늘어나면 어떻게 하나요?
다음 run_tests 호출에서 회귀가 드러나며, 루프는 종료되거나 턴 한도에 도달할 때까지 계속됩니다. 애플리케이션은 복구할 수 있도록 사본을 보관해야 합니다.
DeepSeek V4 Pro는 퇴역하나요?
DeepSeek는 V4.1 Flash 출시 직후 V4 Pro 단계적 종료를 계획했으나, 사용자 요구로 결정을 번복했습니다. V4 Pro는 동일한 과금으로 계속 제공됩니다.
apply_patch를 DeepSeek 외 다른 모델과도 사용할 수 있나요?
형식은 OpenAI의 Codex 도구에서 유래했으며, DeepSeek는 지원을 "Codex 호환성"이라고 설명합니다. 다른 API는 동일한 도구 선언을 지원하는 경우에만 {"type": "custom", "name": "apply_patch"}를 허용합니다.
DeepSeek V4.1 Flash를 로컬에서 실행할 수 있나요?
가능합니다. 모델 가중치는 MIT 라이선스로 Hugging Face에 공개되어 있습니다. 이 튜토리얼은 DeepSeek의 호스팅 API를 사용하며, 모델 서빙이나 하드웨어 요구 사항은 다루지 않습니다.