Troubleshooting · 上下文視窗

prompt is too long / context_length_exceeded — 把上下文視窗塞滿的,通常不是你剛發出去的那一句

30 秒版
  1. 限制是對整段對話,不是對你最後那句——每次請求都把前面全量重發,所以壓垮它的往往是一句短話。
  2. /compact 壓縮,不夠再 /clear 重開;長期用 .claudeignore 把不該進上下文的目錄擋掉。
  3. 失敗的請求不會縮短歷史,所以一炸就會連著炸——必須主動壓縮,等是等不好的。

這個上限管的是整段對話,不是你最後那條訊息。每一次請求都會把之前的所有東西重發一遍——先前的每一輪、讀過的每一個檔案、每一條工具結果。所以真正撐爆的那條訊息,往往只有一行。而且有三種不同的上限,報錯文字幾乎一模一樣;其中只有一種能靠少發輸入解決。

你看到的報錯

以下任意一種
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: <your-count> tokens > <limit> maximum"}}

{"error":{"message":"This model's maximum context length is <limit> tokens. However, your messages resulted in <your-count> tokens. Please reduce the length of the messages.","type":"invalid_request_error","param":"messages","code":"context_length_exceeded"}}

{"type":"error","error":{"type":"invalid_request_error","message":"input length and `max_tokens` exceed context limit: <input> + <max_tokens> > <limit>, decrease input length or `max_tokens` and try again"}}

{"type":"error","error":{"type":"invalid_request_error","message":"max_tokens: <your-value> > <limit>, which is the maximum allowed number of output tokens for <模型 id>"}}

這裡的數字一律寫成 <...>,因為它們每個模型、每個端點都不一樣——只有你自己報錯裡的那組數字才值得看。

措辭也會變。有些端點把第三種縮短成 input length exceeds context limit,有些則去掉 JSON 外殼,只列印 prompt is too long。同一個上限,換個包裝而已——兩種寫法都要搜。

前兩種是同一個問題。第三種是這個問題再加上你給輸出預留的空間。第四種是換了一身相似措辭的另一個上限,把輸入縮小完全動不到它。

三個都被叫作「context length」的上限

管的是什麼會出現的報錯文字真正有效的解法
輸入/上下文視窗你在一次請求裡發出的所有東西:系統提示、工具定義、先前的每一輪、每一條工具結果、每一個附上的檔案prompt is too long · maximum context length · context_length_exceeded從對話裡移掉內容
輸出上限(max_tokens模型最多能寫回多少max_tokens: <n> > <limit>調低 max_tokens。你的輸入無關
輸入+輸出合計輸入和你要求的 max_tokens 必須同時塞得進視窗input length and max_tokens exceed context limit兩邊動哪邊都行——調低 max_tokens縮小輸入

表格第三行是最讓人意外的:一個請求可能在還沒生出任何一個輸出 token 之前就被拒絕,因為你給答案預留的空間,已經跟問題擠不下了。其實沒有任何東西太長,是預留得太貪心。

為什麼你「只發了一行」它也會炸

沒有「單條訊息」的上限這回事。每一次請求都會再帶上整段對話,所以第 N 條訊息的代價,是 1 到 N 的總和。最後那條訊息不是原因,它只是壓垮駱駝的最後一根稻草。

真正佔住視窗的是這些,大致按「被低估的程度」排序:

  1. 工具結果。對一個還留著 node_modules 的倉庫列一次目錄、讀一次 lock 檔案、在整棵樹上做一次沒收斂範圍的搜尋。在對話記錄裡,這些看起來只是一行短短的輸出。在請求裡不是。
  2. 早先讀過、之後一直沒放掉的檔案。第 2 步讀的那個檔案,到了第 40 步還在被重發。
  3. 會話一開始就自動載入的專案指示檔案。你從來沒看它們滾過螢幕,但它們在每一次請求裡。
  4. 對話本身——大家第一個怪罪的那塊,通常也是最小的那塊。

有兩個推論值得記牢:

  • 失敗的請求不會讓歷史變短。這就是為什麼一次溢位之後緊接著又一次:重試發的是同一份超大的請求內容。你得先移掉一些東西,再重試。
  • 視窗填得最快的時候,是一個順利進行的長任務,不是一個難搞的任務。一大堆全都成功的小工具呼叫,就是最典型的樣子。

按這個順序排查

1 · 動任何東西之前,先讀那兩個數字#

上面每一種報錯裡,都同時有你的大小和上限。兩者的比例決定策略:

  • 只超出一點 → 壓縮對話(第 3 步)。
  • 超出好幾倍 → 有不該進來的大東西被拉進來了(第 5 步)。
  • 光算輸入明明離上限還很遠,報錯裡卻點名 max_tokens → 直接跳到第 6 步。

沒看比例就開始猜,是這件事拖掉一個下午的原因。

2 · 先搞清楚你實際用掉多少#

先問客戶端——它知道自己一直在發什麼:

客戶端內
/context

想在客戶端之外拿到精確數字,可以請端點只數一個請求、不真的執行它:

終端
# <模型 id>:從 https://api.9coding.com/v1/models 裡複製一個
curl -sS https://api.9coding.com/v1/messages/count_tokens \
  -H "Authorization: Bearer sk-9c-xxxxxxxx" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"<模型 id>","messages":[{"role":"user","content":"paste the text you are about to send"}]}'

不是每個相容端點都實現了這條路由。如果回 404,退而求其次做個數量級的估算:

終端
wc -c < the-file-you-are-about-to-paste.txt

粗略的尺度是:英文散文大約幾個字元一個 token,原始碼更少,中日韓文字則可能接近一個字一個 token。這個估算只能用來判斷某個東西是不是明顯太大——絕不能用來判斷某個東西是不是剛好塞得下。

3 · 先壓縮,再清空#

客戶端內
/compact

它把對話記錄換成一份摘要,然後接著做下去。要在你還沒被逼到的時候做,不是被逼到之後——壓縮本身得把歷史再發一次才能摘要,而一段已經超過上限的對話,可能連這一次都發不出去。

4 · 壓縮不夠用時就清空#

客戶端內
/clear

/compact 留下一份摘要,/clear 什麼都不留。這個差別很重要:如果撐爆視窗的是某一條超大的工具結果,那它的摘要仍然是它的摘要,可能會把一大部分分量帶到後面去。壓縮完馬上又出現同樣的溢位,就改用清空,然後用三句話把任務重講一次。

5 · 別讓倉庫把自己載入進視窗#

決定排除什麼之前,先找出哪些東西大:

終端
du -sh -- */ 2>/dev/null | sort -h | tail -15
find . -type f -not -path './.git/*' | wc -l

然後把那幾個慣犯完全擋在上下文外面:

.claudeignore
node_modules/
dist/
build/
.next/
vendor/
coverage/
logs/
*.min.js
*.map
package-lock.json
pnpm-lock.yaml
*.png
*.jpg
*.pdf
*.csv
把這份清單放進你的 ignore 檔案——Claude Code 是 .claudeignore。在你依賴它之前,先確認你自己正在跑的那個客戶端版本實際認的檔名與生效範圍。

lock 檔案和壓縮過的打包檔案值得特別點名:它們是單個檔案,在目錄列表裡看起來人畜無害,卻經常是整個倉庫裡最大的一段文字。

也順便改一下你看檔案的方式:能用帶幾行上下文的定點搜尋,就別整個檔案讀;能讀一段範圍,就別讀整個模組。每少讀一個完整檔案,就多留下一塊視窗。

6 · 報錯裡點名 max_tokens 的話,調的是它,不是你的輸入#

終端
# <模型 id>:從 https://api.9coding.com/v1/models 裡複製一個
# 下面的 max_tokens 只是個隨便取的探測值,不是文件寫的上限
curl -sS -w '\n%{http_code}\n' https://api.9coding.com/v1/messages \
  -H "Authorization: Bearer sk-9c-xxxxxxxx" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"<模型 id>","max_tokens":1024,"messages":[{"role":"user","content":"hi"}]}'

這種最小請求成功、你真正那個請求失敗,問題就在輸入。如果連這個也失敗,就是 max_tokens 設得超過那個模型允許的值,怎麼刪都沒用。

7 · 還是塞不下,就是任務本身太大#

拆開不是權宜之計,它就是解法。一個會話做一件任務,每件的產出寫進檔案、而不是留在對話裡帶著走,這樣每次請求都很小,而且——最被低估的那一點——可以重跑。一個做了四小時、最後以溢位收場的會話,是重跑不了的。四個短的可以。

什麼時候這不是你的問題

  • 同一份 prompt 在某個端點塞得下,在另一個端點塞不下。閘道器可能把視窗壓在低於模型本身支援的上限。拿它跟模型文件寫的上限比一比;兩者對不上,那就是端點的策略,不是你發的內容。
  • 不管你刪掉什麼,它都在完全相同的大小失敗。固定開銷——系統提示、工具 schema——吃掉了一塊你在對話裡編輯不到的份額。把啟用的工具數量減少。
  • 今天突然開始失敗,而你的配置完全沒動。可用視窗大小和預設 max_tokens 都是端點那一側的配置,是會變的。
  • 連壓縮本身都失敗。這是唯一一種你真的沒辦法原地救回來的情況;開一個新的會話。

一個不公開自己實際上下文上限的端點,是你沒辦法規劃長任務的端點——你只能靠撞上去,才知道天花板在哪。在你花一下午重寫 prompt 之前,先去看運營方的狀態頁——我們的在狀態頁

提問題時請帶上 request id——9Coding 的報錯響應裡都有一個,形如 (request id: 2026...)。這個 id 加上時間戳、模型名和完整報錯文字,就能把那一次呼叫查出來。只說「用不了」的反饋沒法排查。

不是同一個問題

429 表示請求大小沒問題,是你發太多次了——請看 429 Too Many Requests。帶 invalid_request_error、但報錯裡沒有任何大小數字的 400,是請求格式有問題,不是請求太大——請看多輪對話裡的 400 Bad Request。這兩種都在別的頁。

相關報錯