![]()
凌晨3點,你的支付系統突然收到47次相同的扣款請求。不是攻擊,是一個AI代理(AI Agent,自主執行任務的智能程序)在循環重試。它沒看明白你的文檔,以為"創建訂單"就是"扣款"。
這不是科幻。LangChain、OpenAI Function Calling這些框架里的代理,正在以每秒20次的速度調用API。它們不讀你的落地頁,直接解析OpenAPI規范(一種機器可讀的API描述格式)。描述寫錯一個字,代理就走錯一條路。
你的API是給前端開發者設計的——人類會猶豫、會確認、會在報錯時停下來想。AI不會。它們把模糊描述當真理,把臨時故障當永久失敗,把重試機制當無限循環的許可證。
補丁1:把"顯然"寫進代碼注釋里
人類開發者看文檔能猜意圖。AI代理只看OpenAPI規范里的description字段,而且照字面執行。
下面這個例子來自原文,展示了什么叫"差一個字,錯一條路":
```python class OrderCreate(BaseModel): """Create a new order. Does NOT charge the customer. Use POST /orders/{order_id}/confirm to finalize and charge.""" customer_id: str = Field( description="Unique customer identifier. Format: cust_xxxxxxxxxxxx" ) items: list[str] = Field( description="List of SKU strings. Each SKU must exist in the product catalog." ) amount_cents: int = Field( description="Total order amount in USD cents. Must match sum of item prices.", ge=1, ) ```
注意那個大寫的"Does NOT charge the customer"。沒有這句話,代理可能直接調用創建訂單然后告訴用戶"已完成支付"。30分鐘后草稿過期,用戶的錢沒扣,貨也沒發,客服工單爆炸。
三個代理需要、但人類自己腦補的東西:
第一,把規范暴露在標準端點,比如/openapi.json。代理發現API靠這個,不是你的營銷頁面。
第二,字段描述要包含格式約束。"cust_xxxxxxxxxxxx"比"customer ID"讓代理少打一次試錯電話。
第三,端點summary必須說明副作用。"Create a draft order (does not charge customer)"——括號里的否定句,是防止凌晨3點災難的保險。
補丁2:冪等鍵不是可選項,是生存必需品
每個代理框架都內置重試邏輯。LangChain遇到異常就重試。OpenAI的函數調用遇到格式錯誤就重試。你自己的代理循環遇到超時也重試。
沒有冪等鍵(Idempotency Key,保證同一操作多次執行結果相同的唯一標識),每次重試都是一筆新訂單。一個"創建訂單"的意圖,變成三個訂單、三筆扣款、三個物流包裹。
原文給了一個最小可行實現:
```python @app.post("/payments") async def create_payment( amount_cents: int, customer_id: str, response: Response, idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"), ): if idempotency_key is None: raise HTTPException(status_code=400, detail="Idempotency-Key required") # 檢查是否處理過 if idempotency_key in idempotency_store: return idempotency_store[idempotency_key]["response"] # 處理支付,存儲結果 result = process_payment(amount_cents, customer_id) idempotency_store[idempotency_key] = { "response": result, "timestamp": time.time() } return result ```
生產環境用Redis或數據庫替代那個內存字典。TTL設24小時足夠覆蓋大多數代理的超時窗口。
![]()
關鍵細節:代理不會自己生成冪等鍵。你的API要在首次返回時明確告訴它"下次帶這個鍵來",或者在規范里寫明鍵的生成規則。否則代理會傻乎乎地重試,然后被你返回的"重復鍵錯誤"搞懵。
補丁3:把"部分成功"設計成一等公民
人類看到"3個成功,2個失敗"會手動處理。代理看到200狀態碼就認為全成功了,或者直接崩潰。
批量操作要返回詳細結果數組,每個元素有自己的狀態碼和錯誤信息。不要用一個總狀態碼掩蓋局部失敗。
錯誤信息要機器可讀。"Invalid SKU"對代理沒用。"SKU 'ABC-123' not found in catalog. Valid format: XXX-NNNN. Did you mean 'ABC-124'?"——這種錯誤代理能自己修,不用叫醒你。
原文沒提但值得補充:考慮給代理專用的" dry-run"端點。讓它們在真正執行前先模擬一遍,返回"我會調用A、B、C三個端點,預計產生X、Y、Z副作用"。人類不看這個,但代理能用它做自我檢查。
補丁4:速率限制要分人類和代理兩本賬
你的API可能已經有速率限制。但那是給人類點的按鈕設計的——每秒5次夠用了。
代理的循環可能在一秒內觸發20次調用。不是惡意,是它們在鏈式推理:查庫存→算價格→應用優惠→創建草稿→確認支付→通知倉庫。六步流程,人類要6分鐘,代理要6秒。
分桶限速:人類令牌桶寬松,代理令牌桶更寬松但要求強制身份標識。讓代理在Header里聲明"我是Claude-3-Sonnet-20240229-v1",你就能區分是用戶在測試還是代理在暴走。
更激進的方案:給代理專用端點。/v1/agent/orders 和 /v1/orders 共享業務邏輯,但前者接受更復雜的查詢參數,返回更結構化的錯誤,允許更高的并發。人類開發者不會用這些端點——太啰嗦。代理不會用普通端點——太慢太脆。
補丁5:把"不確定"設計成可恢復的狀態
代理遇到未預期響應時的默認行為是重試。如果你的API返回500,它會等兩秒再試。再500,等四秒。再500,等八秒。指數退避,直到成功或超時。
但有些失敗不該重試。信用卡余額不足,重試100次也是不足。庫存為零,重試100次也是零。這些要返回明確的4xx狀態碼,讓代理知道"這不是臨時故障,是業務規則阻止"。
更微妙的是"我不確定"狀態。支付網關超時,你不知道錢扣了沒。對人類,你顯示"處理中,請稍后查詢"。對代理,你要返回一個"pending"狀態和一個查詢端點,讓它能輪詢而不是重試創建。
原文強調的最后一個細節:代理會記住你的錯誤模式。如果一個端點經常500,代理框架的日志里會積累失敗記錄。下次規劃任務時,它可能繞開你——不是惡意,是優化。你的API從"不太好用"變成"盡量不用",只需要三次超時。
凌晨3點的那47次重試,最后怎么解決的?
原文沒講。但你可以檢查自己的日志:有沒有代理在循環調用同一個端點?有沒有創建后從未確認的草稿訂單?有沒有支付網關超時后代理直接放棄,留下懸而未決的交易?
這些問題現在不解決,等代理用戶占比從1%漲到50%時,你的 on-call 工程師會先崩潰。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.