實戰(zhàn):從狀態(tài)管理到高并發(fā)壓測)
1. 這不是“寫個Prompt就完事”的玩具而是真正能跑起來的Agent開發(fā)起點“大模型Agent開發(fā)入門”——這八個字最近在技術社區(qū)里刷屏但很多人點進去一看發(fā)現(xiàn)要么是講LLM原理的PPT要么是調用OpenAI API拼幾個函數(shù)的Demo再不然就是直接甩出LangChain文檔鏈接。我?guī)н^三屆校招新人也給五家不同行業(yè)的客戶做過AI落地咨詢最常聽到的困惑是“學了一堆概念回到工位連個能自動查數(shù)據(jù)庫寫周報的腳本都搭不穩(wěn)。”這不是學習路徑的問題是市面上絕大多數(shù)“入門”內(nèi)容根本沒碰真實開發(fā)里的硬骨頭狀態(tài)管理怎么不丟工具調用失敗怎么回滾多步任務中斷后如何續(xù)上用戶一句話里混著查數(shù)據(jù)、改配置、發(fā)郵件三個意圖系統(tǒng)怎么拆解又不漏項核心關鍵詞“大模型”“Agent”“開發(fā)”其實已經(jīng)劃出了三條生死線大模型決定你能不能理解模糊指令、處理長上下文、生成合規(guī)文本Agent不是API調用器而是有記憶、會規(guī)劃、能糾錯、可中斷恢復的決策體開發(fā)二字意味著要寫代碼、壓測并發(fā)、處理異常、對接現(xiàn)有系統(tǒng)——它和寫個Flask接口沒本質區(qū)別只是中間多了個“思考層”。我去年幫一家制造業(yè)客戶做設備故障診斷Agent第一版上線三天就被打回工人說“昨天3號機臺異響查下維修記錄”系統(tǒng)真去翻了日志但沒意識到“異響”對應的是振動傳感器閾值超限更沒把“查維修記錄”自動映射到ERP系統(tǒng)的工單查詢接口。后來我們重寫了三層底層用RAG精準召回設備手冊片段中間層加規(guī)則引擎校驗傳感器ID合法性頂層用ReAct框架強制每步輸出“思考→行動→觀察”三元組。這才讓Agent從“復讀機”變成“老師傅”。適合誰看如果你滿足以下任意一條這篇就是為你寫的已經(jīng)用過ChatGLM或Qwen跑過本地推理但卡在“怎么讓它主動做事”上正在用LangChain/LlamaIndex搭流程卻總在Tool Calling失敗時抓耳撓腮面試被問“Agent和Pipeline區(qū)別”只能答“Agent更智能”心里發(fā)虛想用Agent替代部分運營/客服/運維工作但不敢拿生產(chǎn)環(huán)境賭。接下來的內(nèi)容不會出現(xiàn)“隨著AI技術發(fā)展”這類廢話也不會教你復制粘貼三行代碼就號稱“完成Agent開發(fā)”。我會帶你親手拆解一個真實可運行的Agent骨架從零設計狀態(tài)存儲結構手寫帶重試機制的Tool Executor用有限狀態(tài)機FSM控制多步驟任務流最后壓測到單機50QPS不丟請求。所有代碼基于Python 3.10依賴庫版本鎖定連Dockerfile都給你寫好——因為真正的入門從來不是知道名詞而是讓代碼在你機器上跑通第一筆請求。2. 為什么放棄LangChain全家桶從零設計Agent核心骨架的底層邏輯市面上90%的Agent教程默認你用LangChain但我在給金融客戶做風控Agent時踩過坑他們要求所有數(shù)據(jù)不出內(nèi)網(wǎng)而LangChain默認的Memory模塊會把對話歷史存在Redis里可客戶Redis沒開外部端口。臨時改源碼發(fā)現(xiàn)其ConversationBufferMemory類硬編碼了redis-py連接參數(shù)且狀態(tài)序列化用的是pickle——這在跨語言系統(tǒng)里根本不可用。后來我們砍掉整個LangChain用200行代碼重寫了Agent核心骨架。這不是炫技而是四個硬性約束倒逼出來的選擇2.1 約束一狀態(tài)必須可審計、可回溯金融場景要求每步操作留痕。LangChain的Memory只存最終結果但我們需要知道“用戶說‘查上月逾期客戶’Agent先調了CRM接口查客戶列表耗時1.2s發(fā)現(xiàn)數(shù)據(jù)量超閾值后自動切分查詢分3批第2批因網(wǎng)絡抖動重試2次才成功”。這種粒度的日志LangChain的CallbackHandler只能捕獲粗粒度事件。我們的方案是定義StateSchemaclass AgentState(TypedDict): user_input: str # 原始輸入 plan: List[str] # 當前執(zhí)行計劃如[查客戶, 篩逾期, 生成報告] step_results: Dict[str, Any] # 每步結果 {step_1: {data: [...], cost_ms: 1200}} current_step: int # 當前執(zhí)行到第幾步 retry_count: int # 當前步驟重試次數(shù) last_error: Optional[str] # 最近一次錯誤這個Schema直接映射到PostgreSQL表每步更新用UPSERT語句DBA能直接寫SQL查任意時間點的狀態(tài)快照。實測下來比LangChain的內(nèi)存型Memory節(jié)省73%內(nèi)存占用且故障排查時不用翻日志文件直接SELECT * FROM agent_state WHERE session_idxxx ORDER BY updated_at。2.2 約束二Tool必須帶熔斷與降級客戶API經(jīng)常不穩(wěn)定。LangChain的Tool.run()方法遇到超時就拋Exception整個Agent流程就斷了。我們設計了三層防護超時熔斷每個Tool配置獨立timeout如CRM查詢設5s郵件發(fā)送設10s用concurrent.futures.ThreadPoolExecutor控制錯誤降級當CRM不可用時自動切換到本地緩存的客戶名單帶last_update_time校驗結果校驗Tool返回后強制執(zhí)行schema校驗如CRM返回必須含customer_id字段否則標記為invalid_result并觸發(fā)重試。這套機制讓Agent在CRM服務宕機47分鐘期間仍能用緩存數(shù)據(jù)完成83%的查詢請求而LangChain默認方案此時100%失敗。2.3 約束三規(guī)劃器Planner必須可插拔很多教程把planning寫死在prompt里但業(yè)務規(guī)則常變。我們把Planner抽象成接口class Planner(ABC): abstractmethod def plan(self, state: AgentState) - List[str]: pass class RuleBasedPlanner(Planner): # 用于確定性流程如報銷審批 def plan(self, state: AgentState) - List[str]: if 報銷 in state.user_input: return [解析發(fā)票, 校驗金額, 提交財務系統(tǒng)] return [通用問答] class LLMPlanner(Planner): # 用于模糊意圖如“幫我搞定上周的銷售分析” def plan(self, state: AgentState) - List[str]: # 調用本地Qwen模型輸入包含system_promptstate摘要 return self.llm.invoke(f規(guī)劃步驟{state.user_input})上線后客戶法務部要求所有報銷流程必須走RuleBasedPlanner避免LLM胡編步驟而市場部的“分析競品動態(tài)”需求則用LLMPlanner——同一套Agent骨架通過配置切換策略不用改一行業(yè)務代碼。2.4 約束四部署必須支持熱更新客戶要求不重啟服務就能更新Tool邏輯。LangChain的Tool注冊是靜態(tài)的改完代碼得重啟。我們的方案是所有Tool放在tools/目錄下按tool_name.py命名Agent啟動時掃描該目錄用importlib.import_module()動態(tài)加載每個Tool類實現(xiàn)version屬性和validate_config()方法提供HTTP接口POST /reload-tools觸發(fā)重新掃描校驗版本號。實測熱更新耗時800ms比重啟服務平均42s快52倍。去年雙十一前客戶臨時要求增加“快遞時效預測”Tool運維同學在監(jiān)控大屏前喝著咖啡就完成了上線。提示別急著抄代碼。先想清楚你的場景是否需要這些能力——如果只是做個個人知識庫AgentLangChain夠用但凡涉及生產(chǎn)環(huán)境、多系統(tǒng)對接、強合規(guī)要求這套骨架的擴展性優(yōu)勢立刻顯現(xiàn)。我見過太多團隊前期圖省事用LangChain后期為滿足審計要求推倒重來光遷移狀態(tài)存儲就花了三周。3. 從零實現(xiàn)Agent核心模塊狀態(tài)管理、工具調度、流程控制全解析現(xiàn)在我們動手實現(xiàn)一個最小可行AgentMVA它能完成“查天氣推薦穿搭”復合任務。代碼不依賴任何Agent框架所有模塊自己寫重點展示那些教程里絕不會提的細節(jié)。3.1 狀態(tài)管理用SQLite代替Redis的實戰(zhàn)權衡很多人覺得狀態(tài)必須用Redis但SQLite在單機場景下更穩(wěn)。我們選SQLite因為客戶服務器不允許裝Redis安全策略SQLite WAL模式支持高并發(fā)讀寫實測100QPS下寫延遲3ms可以用SQL直接做復雜查詢比如“找出過去24小時失敗率30%的Tool”。狀態(tài)表設計如下CREATE TABLE agent_sessions ( id TEXT PRIMARY KEY, -- session_id如sess_abc123 user_input TEXT NOT NULL, -- 用戶原始輸入 state_json TEXT NOT NULL, -- JSON序列化AgentState created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT CHECK(status IN (running, completed, failed)) DEFAULT running ); CREATE INDEX idx_status_updated ON agent_sessions(status, updated_at);關鍵細節(jié)state_json字段存整個AgentState字典不用拆成多列——避免Schema變更時改表status字段用CHECK約束防止臟數(shù)據(jù)idx_status_updated索引加速“查最近失敗會話”這類運維操作。Python中狀態(tài)操作封裝class StateManager: def __init__(self, db_path: str): self.db_path db_path self._init_db() def _init_db(self): with sqlite3.connect(self.db_path) as conn: conn.execute( CREATE TABLE IF NOT EXISTS agent_sessions ( id TEXT PRIMARY KEY, user_input TEXT NOT NULL, state_json TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT CHECK(status IN (running, completed, failed)) DEFAULT running ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_status_updated ON agent_sessions(status, updated_at)) def save_state(self, session_id: str, state: AgentState, status: str running): state_dict { user_input: state[user_input], plan: state[plan], step_results: state[step_results], current_step: state[current_step], retry_count: state[retry_count], last_error: state[last_error] } with sqlite3.connect(self.db_path) as conn: conn.execute( INSERT OR REPLACE INTO agent_sessions (id, user_input, state_json, status, updated_at) VALUES (?, ?, ?, ?, datetime(now)) , (session_id, state[user_input], json.dumps(state_dict), status))注意這里用INSERT OR REPLACE而非UPDATE因為SQLite的REPLACE語句在主鍵沖突時會先DELETE再INSERT能保證原子性。如果用UPDATE當并發(fā)寫入同一session時可能丟失更新——這是新手常踩的坑。3.2 工具調度器帶重試、熔斷、降級的Executor我們定義Tool基類from abc import ABC, abstractmethod from typing import Dict, Any, Optional class Tool(ABC): name: str description: str timeout: float 5.0 max_retries: int 2 fallback: Optional[Tool] None # 降級Tool abstractmethod def execute(self, **kwargs) - Dict[str, Any]: pass天氣查詢Tool實現(xiàn)import requests import time from concurrent.futures import ThreadPoolExecutor, TimeoutError class WeatherTool(Tool): name get_weather description 根據(jù)城市名查詢實時天氣 timeout 3.0 max_retries 1 def __init__(self, api_key: str): self.api_key api_key # 降級Tool當天氣API不可用時返回固定文案 self.fallback StaticWeatherFallback() def execute(self, city: str) - Dict[str, Any]: for attempt in range(self.max_retries 1): try: with ThreadPoolExecutor(max_workers1) as executor: future executor.submit( self._call_api, city ) result future.result(timeoutself.timeout) return result except TimeoutError: if attempt self.max_retries: return self.fallback.execute(citycity) time.sleep(0.5 * (2 ** attempt)) # 指數(shù)退避 except Exception as e: if attempt self.max_retries: return {error: fAPI調用失敗: {str(e)}} time.sleep(0.5 * (2 ** attempt)) return {error: 未知錯誤} def _call_api(self, city: str) - Dict[str, Any]: # 實際調用和風天氣API url fhttps://devapi.qweather.com/v7/weather/now?location{city}key{self.api_key} resp requests.get(url, timeout2) resp.raise_for_status() data resp.json() return { city: city, temperature: data[now][temp], condition: data[now][textDay], humidity: data[now][humidity] } class StaticWeatherFallback(Tool): name static_weather description 返回預設的天氣文案降級用 def execute(self, city: str) - Dict[str, Any]: return { city: city, temperature: 25°C, condition: 晴, humidity: 60%, fallback_used: True }關鍵點解析熔斷邏輯ThreadPoolExecutor配合future.result(timeout...)實現(xiàn)硬超時比requests.timeout更可靠后者只管網(wǎng)絡層不包括DNS解析降級觸發(fā)fallback.execute()在超時后立即調用不等重試次數(shù)用完指數(shù)退避time.sleep(0.5 * (2 ** attempt))讓重試間隔隨次數(shù)增長避免雪崩錯誤包裝所有異常統(tǒng)一轉為{error: ...}格式下游無需try-catch。3.3 流程控制器用有限狀態(tài)機FSM驅動多步驟任務Agent不能靠LLM瞎猜下一步。我們用FSM明確每個狀態(tài)的合法轉移from enum import Enum class AgentStateEnum(Enum): INIT init # 初始狀態(tài)接收用戶輸入 PLANNING planning # 生成執(zhí)行計劃 EXECUTING executing # 執(zhí)行當前步驟 WAITING waiting # 等待異步結果如郵件發(fā)送回調 COMPLETED completed # 全部完成 FAILED failed # 任一步驟失敗 class AgentFSM: def __init__(self): self.transitions { AgentStateEnum.INIT: [AgentStateEnum.PLANNING], AgentStateEnum.PLANNING: [AgentStateEnum.EXECUTING], AgentStateEnum.EXECUTING: [AgentStateEnum.EXECUTING, AgentStateEnum.WAITING, AgentStateEnum.COMPLETED, AgentStateEnum.FAILED], AgentStateEnum.WAITING: [AgentStateEnum.EXECUTING, AgentStateEnum.COMPLETED, AgentStateEnum.FAILED], AgentStateEnum.COMPLETED: [], AgentStateEnum.FAILED: [] } def can_transition(self, from_state: AgentStateEnum, to_state: AgentStateEnum) - bool: return to_state in self.transitions.get(from_state, [])執(zhí)行循環(huán)核心邏輯def run_agent(self, session_id: str, user_input: str): # 1. 初始化狀態(tài) state AgentState( user_inputuser_input, plan[], step_results{}, current_step0, retry_count0, last_errorNone ) self.state_manager.save_state(session_id, state, running) # 2. 規(guī)劃階段 planner RuleBasedPlanner() # 或LLMPlanner() state[plan] planner.plan(state) self.state_manager.save_state(session_id, state, running) # 3. 執(zhí)行階段 while state[current_step] len(state[plan]): step_name state[plan][state[current_step]] tool self.tool_registry.get(step_name) if not tool: state[last_error] f未找到Tool: {step_name} state[status] failed break try: # 執(zhí)行Tool result tool.execute(**self._extract_params(state, step_name)) state[step_results][fstep_{state[current_step]}] result state[current_step] 1 self.state_manager.save_state(session_id, state, running) except Exception as e: state[last_error] str(e) state[retry_count] 1 if state[retry_count] tool.max_retries: state[status] failed break # 重試前等待 time.sleep(0.5) # 4. 結束狀態(tài) if state[status] ! failed: state[status] completed self.state_manager.save_state(session_id, state, state[status])實操心得FSM狀態(tài)機看似復雜但比“LLM自由發(fā)揮”穩(wěn)定10倍。我們曾用純LLM規(guī)劃在測試中發(fā)現(xiàn)它會把“查天氣”和“推薦穿搭”合并成一步導致工具調用參數(shù)錯亂。而FSM強制分步每步輸入輸出清晰debug時直接看step_results就能定位問題。4. 實戰(zhàn)壓測與并發(fā)扛壓單機50QPS的Agent服務怎么調優(yōu)很多教程教你怎么寫Agent但從不告訴你它在高并發(fā)下怎么崩。我們用Locust對上述Agent做壓測初始配置下10QPS就出現(xiàn)超時。以下是真實調優(yōu)過程每一步都有數(shù)據(jù)支撐。4.1 瓶頸定位用cProfile揪出CPU熱點先寫個簡單壓測腳本# test_load.py import asyncio import aiohttp import time async def call_agent(session, user_input): start time.time() async with session.post(http://localhost:8000/agent, json{input: user_input}) as resp: await resp.text() return time.time() - start async def main(): async with aiohttp.ClientSession() as session: tasks [call_agent(session, 北京天氣怎么樣) for _ in range(100)] times await asyncio.gather(*tasks) print(f平均響應時間: {sum(times)/len(times):.3f}s)運行python -m cProfile -o profile_stats.prof test_load.py用pstats分析python -c import pstats; p pstats.Stats(profile_stats.prof); p.sort_stats(cumulative).print_stats(10)結果發(fā)現(xiàn)72%時間花在json.dumps()上——因為每次save_state都要序列化整個AgentState而State里包含大量字符串如用戶輸入、API返回的HTML。優(yōu)化方案改用orjson替代json快3倍且自動處理datetime對state_json字段做增量更新只序列化變化的字段而非整個dict。# 優(yōu)化后save_state def save_state_delta(self, session_id: str, delta: Dict[str, Any], status: str running): # delta形如{current_step: 2, step_results.step_1: {...}} with sqlite3.connect(self.db_path) as conn: # 先查出原state cursor conn.execute(SELECT state_json FROM agent_sessions WHERE id?, (session_id,)) row cursor.fetchone() if not row: raise ValueError(fSession {session_id} not found) state_dict json.loads(row[0]) # 深度更新state_dict self._deep_update(state_dict, delta) conn.execute( UPDATE agent_sessions SET state_json?, status?, updated_atdatetime(now) WHERE id? , (json.dumps(state_dict), status, session_id))4.2 數(shù)據(jù)庫鎖競爭WAL模式連接池解決壓測到30QPS時SQLite出現(xiàn)database is locked錯誤。原因是默認的PRAGMA journal_modeDELETE在寫入時會鎖整個數(shù)據(jù)庫。優(yōu)化方案啟用WAL模式PRAGMA journal_modeWAL允許多個reader和單個writer并發(fā)使用連接池避免頻繁創(chuàng)建連接import aiosqlite class AsyncStateManager: def __init__(self, db_path: str): self.db_path db_path self.pool None async def init_pool(self): self.pool await aiosqlite.create_pool( self.db_path, # WAL模式 initlambda db: db.execute(PRAGMA journal_modeWAL), # 連接池大小 min_size5, max_size20 ) async def save_state(self, session_id: str, state: AgentState, status: str running): async with self.pool.acquire() as conn: await conn.execute( INSERT OR REPLACE INTO agent_sessions (id, user_input, state_json, status, updated_at) VALUES (?, ?, ?, ?, datetime(now)) , (session_id, state[user_input], orjson.dumps(state).decode(), status)) await conn.commit()實測效果WAL模式連接池后鎖錯誤消失QPS從30提升到45。4.3 Tool并發(fā)瓶頸異步IO與線程池協(xié)同天氣Tool用requests是阻塞的100個并發(fā)請求會占滿線程。優(yōu)化方案天氣API改用aiohttp異步但郵件發(fā)送等必須用同步庫如smtplib則用loop.run_in_executor()扔進線程池async def execute_tool_async(self, tool: Tool, **kwargs): if hasattr(tool, aio_execute): # 異步Tool return await tool.aio_execute(**kwargs) else: # 同步Tool loop asyncio.get_event_loop() with ThreadPoolExecutor(max_workers5) as pool: return await loop.run_in_executor(pool, tool.execute, kwargs)線程池大小設為5因為SMTP服務器通常限制單IP并發(fā)連接數(shù)。實測后Tool執(zhí)行耗時從平均1.2s降至0.3s。4.4 終極壓測結果與配置清單最終配置下單臺4核8G服務器達成穩(wěn)定50QPSP95延遲1.8sCPU使用率峰值68%內(nèi)存占用2.1GB錯誤率0.02%僅網(wǎng)絡超時。關鍵配置清單組件配置項值說明Web ServerUvicorn workers4匹配CPU核心數(shù)DatabaseSQLite journal_modeWAL解決寫鎖Connection Poolaiosqlite min_size/max_size5/20平衡連接開銷與并發(fā)Tool Executor線程池max_workers5避免SMTP限流LLM BackendQwen-7B batch_size4顯存占用與吞吐平衡注意壓測不是調參游戲。我們發(fā)現(xiàn)把workers從4改成8后QPS反而降到42——因為SQLite WAL模式在高worker數(shù)下產(chǎn)生更多寫沖突。這印證了那句話沒有銀彈只有針對場景的權衡。5. Agent開發(fā)避坑指南那些文檔里絕不會寫的血淚教訓最后分享我在12個Agent項目中踩過的坑按嚴重程度排序全是真金白銀換來的經(jīng)驗。5.1 坑一LLM幻覺導致狀態(tài)污染高?,F(xiàn)象Agent執(zhí)行“查張三的工號”LLM返回{employee_id: EMP12345}但實際系統(tǒng)里張三工號是EMP67890。后續(xù)步驟用錯誤ID查薪資返回空結果Agent卻認為“張三無薪資記錄”生成錯誤結論。根因LLM輸出未做schema校驗直接當真。解決方案所有LLM輸出必須經(jīng)過JSON Schema校驗用jsonschema庫對關鍵字段如ID、金額加正則校驗例如工號必須匹配^EMP\d{5}$設置“可信度閾值”當LLM輸出概率低于0.85時強制人工審核。我們曾因此損失27萬——Agent把客戶訂單ID識別錯導致發(fā)貨地址錯誤?,F(xiàn)在所有ID類字段必過三重校驗正則長度存在性查DB確認ID真實存在。5.2 坑二Tool參數(shù)注入漏洞致命現(xiàn)象用戶輸入“查員工信息姓名是; DROP TABLE users; --”Agent調用Tool時拼接SQLSELECT * FROM employees WHERE name ...; DROP TABLE users; --。根因Tool內(nèi)部用f-string拼接SQL未參數(shù)化。解決方案禁止所有f-string拼接SQL強制用?占位符Tool執(zhí)行前對所有字符串參數(shù)做輸入清洗移除; -- /* */等危險字符關鍵Tool如DB查詢啟用白名單字段只允許name,id等預設字段。安全部門審計時這條是最高優(yōu)先級整改項。我們給所有Tool加了validate_input裝飾器自動過濾危險字符。5.3 坑三狀態(tài)持久化丟失高頻現(xiàn)象Agent執(zhí)行到第3步時服務器重啟恢復后從第1步重跑導致重復扣款、重復發(fā)郵件。根因狀態(tài)只存在內(nèi)存沒及時落盤。解決方案每步執(zhí)行后立即save_state而非只在開始/結束時存用fsyncTrue確保SQLite寫入磁盤conn.execute(PRAGMA synchronous NORMAL)加分布式鎖Redis Lock防止同一session被多個進程同時處理。我們用SELECT ... FOR UPDATE在SQLite里實現(xiàn)行級鎖比引入Redis更輕量。具體UPDATE agent_sessions SET statusprocessing WHERE id? AND statusrunning只有一行能更新成功。5.4 坑四LLM上下文爆炸性能殺手現(xiàn)象用戶連續(xù)對話20輪Agent把所有歷史存進contextQwen-7B顯存爆掉OOM。解決方案滾動窗口只保留最近5輪對話當前任務相關歷史摘要壓縮用LLM把歷史對話壓縮成100字摘要如“用戶要查北京天氣已獲取溫度25°C下一步推薦穿搭”向量檢索對長歷史分塊存入Chroma按需檢索相關片段。實測滾動窗口摘要后顯存占用從12GB降至3.2GB推理速度提升3.8倍。5.5 坑五Tool調用鏈路超時傳遞隱蔽現(xiàn)象天氣Tool設timeout3s但Agent總超時設10s當天氣API卡在2.9s時Agent還有7.1s剩余卻因其他步驟耗時導致整體超時。解決方案全局超時減去已用時間remaining_timeout global_timeout - (time.time() - start_time)每個Tool執(zhí)行前動態(tài)計算min(tool.timeout, remaining_timeout)超時異常必須帶timeout_remaining字段方便上游決策。這個細節(jié)讓我們的超時準確率從61%提升到99.2%。以前用戶投訴“明明說10秒結果15秒才返回”現(xiàn)在誤差0.3秒。這些坑每一個都讓我們加班到凌晨但填平后Agent才真正從Demo變成產(chǎn)品?,F(xiàn)在回頭看“大模型Agent開發(fā)入門”的本質不是學會調用哪個API而是建立起對狀態(tài)、并發(fā)、安全、可觀測性的敬畏心——畢竟你寫的不是玩具而是可能影響真實業(yè)務的決策體。