建可觀測(cè)、可控的自主智能體框架實(shí)踐)
1. 一開(kāi)始為什么要做 agency-agents這幾年智能體這個(gè)概念被炒得很熱各種框架層出不窮??晌易约喊阎髁鞣桨付即盍艘槐橹篌w會(huì)就一個(gè)字虛。表面上都能聊、能調(diào)工具、能多步推理你真拿一個(gè)有壓力的真實(shí)業(yè)務(wù)過(guò)去它就原形畢露了——要么在大模型幻覺(jué)里打轉(zhuǎn)要么把一個(gè)簡(jiǎn)單任務(wù)拆成一堆互相矛盾的子步驟要么壓根不知道什么時(shí)候該停手。我的需求其實(shí)很樸素做一個(gè)真正能自主推進(jìn)任務(wù)的系統(tǒng)而不是prompt 循環(huán) 工具調(diào)用的三件套。項(xiàng)目標(biāo)題里的 agency 不是代理機(jī)構(gòu)這個(gè)意思而是指智能體的能動(dòng)性/自主決策能力。所以這個(gè)項(xiàng)目叫 agency-agents想做的就是把這種自主性落成一個(gè)可工程化、可觀測(cè)、可控制的框架。1.1 那些年我用鏈?zhǔn)骄幣诺谋锴趧?dòng)工之前我習(xí)慣性地先盤(pán)了一遍已有方案的痛點(diǎn)。最常見(jiàn)的問(wèn)題是大家把智能體理解成在一個(gè)循環(huán)里反復(fù)調(diào)用大模型直到它說(shuō)做完。聽(tīng)上去很聰明實(shí)際跑起來(lái)你會(huì)看到它反復(fù)做同一件事或者一次任務(wù)里調(diào)了十幾次模型接口結(jié)果答案還不如一次性直接問(wèn)來(lái)得好。第二個(gè)痛點(diǎn)是編排粒度太粗。很多框架把步驟定義成一個(gè)大函數(shù)開(kāi)發(fā)者只需要塞進(jìn)去一個(gè)角色設(shè)定剩下全靠模型自由發(fā)揮。業(yè)務(wù)一旦復(fù)雜這個(gè)自由發(fā)揮就會(huì)變成災(zāi)難。你沒(méi)法在某個(gè)關(guān)鍵節(jié)點(diǎn)打斷它、修正它也沒(méi)法事后復(fù)盤(pán)它當(dāng)時(shí)為什么選了這條路徑。還有一個(gè)非?,F(xiàn)實(shí)的問(wèn)題多智能體協(xié)作基本靠互相甩 prompt。A 智能體把一大段結(jié)果堆給 B 智能體B 再堆給 C鏈越長(zhǎng)信息損耗和 token 消耗越離譜。更別提多個(gè)智能體同時(shí)操作外部資源時(shí)連最基本的并發(fā)互斥都沒(méi)有。1.2 自主性不是靠多寫(xiě)幾個(gè) prompt 就能實(shí)現(xiàn)的我在設(shè)計(jì) agency-agents 時(shí)給自己定了幾條原則后來(lái)發(fā)現(xiàn)它們幾乎成了整個(gè)項(xiàng)目的地基第一自主性必須建立在顯式任務(wù)圖之上。智能體不是從第一個(gè)字開(kāi)始自由翱翔而是先根據(jù)用戶目標(biāo)做任務(wù)分解再把子任務(wù)組織成一張有依賴關(guān)系的圖。每個(gè)節(jié)點(diǎn)都知道自己的輸入、輸出、可用工具和完成條件。這樣它既能自主決定下一步又能被人類在任意節(jié)點(diǎn)插話。第二每個(gè)決策都要留痕。大模型本身是黑盒如果系統(tǒng)不主動(dòng)記錄它選了哪個(gè)工具、為什么選、輸入輸出是什么、中間有沒(méi)有重試那整個(gè)系統(tǒng)就是不可信的。我把決策日志當(dāng)成一等公民而不是事后從 API 調(diào)用記錄里猜。第三一切可以被量化治理。預(yù)算上限、最大步數(shù)、最大分解深度、工具白名單、超時(shí)時(shí)間這些不能只是配置文件里的擺設(shè)而要在運(yùn)行時(shí)被強(qiáng)制執(zhí)行。自主不等于失控我們要的是在給定邊界內(nèi)的自主。這三條原則聽(tīng)上去簡(jiǎn)單把它們同時(shí)落到一個(gè)可運(yùn)行的代碼架構(gòu)里花了我相當(dāng)長(zhǎng)的時(shí)間。后面幾章我把關(guān)鍵設(shè)計(jì)一個(gè)個(gè)講清楚。2. 核心抽象任務(wù)圖、決策點(diǎn)與可回溯日志agency-agents 的第一版其實(shí)并不復(fù)雜我甚至沒(méi)有用任何現(xiàn)成的 Agent 框架核心就是三個(gè)數(shù)據(jù)結(jié)構(gòu)任務(wù)圖TaskGraph、決策節(jié)點(diǎn)DecisionNode和工具路由ToolRoute。整個(gè)運(yùn)行過(guò)程可以概括為用戶目標(biāo)進(jìn)來(lái)規(guī)劃器生成任務(wù)圖執(zhí)行器沿著圖推進(jìn)每個(gè)節(jié)點(diǎn)上智能體也許要選擇工具、也許要生成子任務(wù)每走一步都寫(xiě)日志。2.1 從線性鏈條到 DAG讓 Agent 自己決定下一步我見(jiàn)過(guò)很多流程編排框架其實(shí)只是一根直線步驟一、步驟二、步驟三最多加個(gè) if 分支。真實(shí)業(yè)務(wù)里任務(wù)之間往往是依賴關(guān)系而不是先后關(guān)系。比如一個(gè)生成競(jìng)品分析報(bào)告的任務(wù)它包含三個(gè)子任務(wù)抓取競(jìng)品頁(yè)面、清洗數(shù)據(jù)、寫(xiě)分析結(jié)論。寫(xiě)分析結(jié)論依賴清洗后的數(shù)據(jù)但抓取和清洗之間并不是嚴(yán)格的先后關(guān)系——你可以直接抓取原始數(shù)據(jù)也可以先從已有數(shù)據(jù)庫(kù)里取。所以我用了一個(gè)有向無(wú)環(huán)圖DAG來(lái)表示任務(wù)。每個(gè)節(jié)點(diǎn)有兩種行為模式原子任務(wù)Atomic Task直接調(diào)用某個(gè)工具完成比如調(diào)用某搜索接口獲取關(guān)鍵詞 Top 100。復(fù)合任務(wù)Composite Task繼續(xù)向下分解出子圖由規(guī)劃器遞歸處理。規(guī)劃器在生成圖的時(shí)候不搞什么都讓大模型自由發(fā)揮的那一套而是先做一次目標(biāo)分析把用戶目標(biāo)拆成幾個(gè)語(yǔ)義塊然后做依賴推導(dǎo)決定哪些塊必須先做、哪些可以并行最后做資源綁定給每個(gè)塊分配可用的工具和預(yù)期產(chǎn)出。提示DAG 不是越復(fù)雜越好。如果任務(wù)本身只有兩個(gè)步驟就老老實(shí)實(shí)走線性流程別硬拆成五六個(gè)節(jié)點(diǎn)。后續(xù)我加了一個(gè)最大分解深度參數(shù)默認(rèn)是三層最多五層超過(guò)就觸發(fā)詢問(wèn)用戶的降級(jí)動(dòng)作防止規(guī)劃器把簡(jiǎn)單任務(wù)切碎。2.2 決策點(diǎn)記錄每一個(gè)為什么都要能在日志里找到答案這是我認(rèn)為整個(gè)項(xiàng)目里最值錢(qián)的設(shè)計(jì)。大多數(shù)框架把模型 API 的 raw response 存下來(lái)就算完事但那些內(nèi)容噪聲太大復(fù)盤(pán)時(shí)根本翻不動(dòng)。我在每個(gè)節(jié)點(diǎn)內(nèi)部增加了三個(gè)固定環(huán)節(jié)意圖分析Intent 這一步到底要做什么期望的產(chǎn)出格式是什么工具選擇Tool Selection 當(dāng)前可選工具列表是什么模型選了哪個(gè)候選得分是多少結(jié)果評(píng)估Evaluation 輸出是否符合 schema 校驗(yàn)是否觸發(fā)重試重試原因是什么每一次執(zhí)行都會(huì)生成一條結(jié)構(gòu)化日志類似{ node_id: report_gen_001, intent: 生成最終分析報(bào)告需包含數(shù)據(jù)摘要與異常解釋, selected_tool: report_writer, candidate_tools: [report_writer, code_interpreter, search_engine], input_summary: 清洗后數(shù)據(jù)表共 38 行, output_summary: 報(bào)告已生成長(zhǎng)度 1200 字, retries: 1, retry_reason: 輸出缺少 異常解釋 段落schema 校驗(yàn)失敗, cost_usd: 0.023, timestamp: 2025-06-19T08:23:19Z }這條日志不僅用于事后審計(jì)它還有一個(gè)實(shí)時(shí)用途當(dāng)重試次數(shù)超過(guò)閾值時(shí)執(zhí)行器會(huì)跳過(guò)當(dāng)前決策把問(wèn)題升級(jí)給人類處理。這樣你就不會(huì)看到智能體在同一個(gè)節(jié)點(diǎn)上糾結(jié)十幾次、燒掉一堆 token 的奇觀。2.3 工具路由不是函數(shù)調(diào)用而是候選列表篩選另一個(gè)容易出問(wèn)題的點(diǎn)是工具調(diào)用。很多框架喜歡做function calling直接把模型的輸出映射到 Python 函數(shù)。這聽(tīng)上去很順但模型經(jīng)常瞎編參數(shù)或者在一個(gè)不需要工具的上下文里強(qiáng)行調(diào)用工具。我在設(shè)計(jì)工具路由時(shí)沒(méi)有走直接映射這條路而是先讓模型從候選列表里選選完再做參數(shù)補(bǔ)全和校驗(yàn)第一步模型返回一個(gè)工具的標(biāo)識(shí)符比如工具 ID而不是一大段 JSON 參數(shù)。第二步系統(tǒng)從工具注冊(cè)表里取出該工具的 JSON Schema讓模型基于這個(gè) Schema 生成參數(shù)。第三步參數(shù)必須通過(guò) pydantic 校驗(yàn)不過(guò)就再給一次機(jī)會(huì)再不過(guò)就詢問(wèn)用戶。這個(gè)設(shè)計(jì)的額外收益是模型不需要記住每個(gè)工具的完整參數(shù)格式它只需要知道這個(gè)工具能干什么、大概需要哪幾類信息。參數(shù)生成在被驗(yàn)證過(guò)的 Schema 約束下進(jìn)行輸出質(zhì)量會(huì)穩(wěn)很多幻覺(jué)參數(shù)的概率明顯下降。3. 多 Agent 協(xié)作消息隊(duì)列、資源租約與記憶隔離單 Agent 的問(wèn)題解決了多 Agent 才是真正的硬骨頭。agency-agents 里支持同時(shí)跑多個(gè)智能體比如一個(gè)負(fù)責(zé)數(shù)據(jù)采集、一個(gè)負(fù)責(zé)數(shù)據(jù)分析、一個(gè)負(fù)責(zé)寫(xiě)作。如果讓它們各跑各的、最后拼起來(lái)你會(huì)發(fā)現(xiàn)協(xié)作兩個(gè)字根本不成立——因?yàn)樗鼈冎g沒(méi)有共同語(yǔ)言。3.1 為什么不能讓 Agent 之間直接互相傳大段文本最開(kāi)始我的方案很簡(jiǎn)單A 智能體把結(jié)果作為一段長(zhǎng)文本塞給 B 智能體B 看完再干活。結(jié)果遇到了兩個(gè)大問(wèn)題。第一是 token 消耗爆炸一份數(shù)據(jù)表如果直接渲染成文本傳過(guò)去一次就得吃掉幾千 token多傳幾輪任務(wù)還沒(méi)做完賬單先瘋了。第二是上下文污染B 智能體分不清哪些是 A 的最終結(jié)論、哪些是 A 的過(guò)程困惑容易被無(wú)關(guān)信息帶偏。后來(lái)我改成消息隊(duì)列 結(jié)構(gòu)化數(shù)據(jù)包的模式。智能體之間的通信不再傳輸對(duì)話文本而是傳輸一個(gè)帶有明確 schema 的數(shù)據(jù)包task_id屬于哪個(gè)父任務(wù)sender/receiver誰(shuí)發(fā)的、誰(shuí)接收payload_type數(shù)據(jù)類型比如table_chunk、summary_section、search_resultspayload_ref指向某個(gè)數(shù)據(jù)存儲(chǔ)位置的引用而不是直接塞大對(duì)象ttl這個(gè)包的有效時(shí)間過(guò)期自動(dòng)丟棄B 智能體拿到的是引用 元信息而非整段文本。它需要具體數(shù)據(jù)的時(shí)候再通過(guò)專門(mén)的數(shù)據(jù)讀取工具去取。這個(gè)改動(dòng)讓多智能體協(xié)作從人傳話變成了倉(cāng)庫(kù)調(diào)度信息失真和 token 浪費(fèi)同時(shí)下降。3.2 資源租約避免兩個(gè) Agent 搶同一個(gè)文件多智能體并發(fā)操作外部資源的時(shí)候一定會(huì)遇到鎖問(wèn)題。比如數(shù)據(jù)采集智能體和數(shù)據(jù)清洗智能體可能同時(shí)訪問(wèn)某個(gè)緩存文件如果沒(méi)有互斥機(jī)制會(huì)出現(xiàn)半寫(xiě)狀態(tài)或者臟讀。我早期用過(guò)一個(gè)笨辦法讓第二個(gè)智能體重試。等一會(huì)兒再讀也許就好了——事實(shí)證明這個(gè)辦法在大多數(shù)情況下只是把問(wèn)題延后。最后我引入了一個(gè)非常簡(jiǎn)單的資源租約表本質(zhì)就是一個(gè) SQLite 表每次工具要訪問(wèn)共享資源前先申請(qǐng)租約CREATE TABLE resource_leases ( resource_key TEXT PRIMARY KEY, lease_holder TEXT NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );工具訪問(wèn)資源的流程變成先嘗試申請(qǐng)租約成功則執(zhí)行操作完成后釋放失敗則等待并重試超過(guò)等待上限就把節(jié)點(diǎn)標(biāo)記為blocked等待人工協(xié)調(diào)。這個(gè)表排除了死鎖的可能因?yàn)樽饧s一定有截止時(shí)間不會(huì)出現(xiàn)等到天荒地老的情況。3.3 記憶要分賬本會(huì)話隔離與長(zhǎng)期知識(shí)的取舍做過(guò)智能體的人都知道記憶是個(gè)雙刃劍。讓智能體記住上一輪對(duì)話它能提供更好的上下文讓它記住太多歷史它就開(kāi)始混淆邊界、把舊任務(wù)的信息套到新任務(wù)上。我在設(shè)計(jì)記憶機(jī)制的時(shí)候用了三本賬會(huì)話記憶Ephemeral只在當(dāng)前 run 內(nèi)有效任務(wù)結(jié)束自動(dòng)清空。存放節(jié)點(diǎn)之間的臨時(shí)輸出。項(xiàng)目記憶Project同一個(gè)項(xiàng)目下共享帶版本號(hào)。比如這個(gè)任務(wù)的圖結(jié)構(gòu)怎么生成的上一次失敗在哪里。長(zhǎng)期知識(shí)Long-term寫(xiě)入獨(dú)立的向量庫(kù)但每次寫(xiě)入必須經(jīng)過(guò)一個(gè)知識(shí)提煉步驟把原始信息壓縮成結(jié)構(gòu)化條目并記錄來(lái)源。核心的取舍是智能體永遠(yuǎn)不要直接讀歷史對(duì)話原文只允許讀提煉后的知識(shí)條目。這樣做避免了上下文污染也降低了 token 成本。4. 一個(gè)完整的實(shí)測(cè)案例自動(dòng)生成競(jìng)品監(jiān)測(cè)日?qǐng)?bào)理論說(shuō)了一堆還是上一個(gè)完整的案例來(lái)講。假設(shè)我們老大要求每天上午十點(diǎn)自動(dòng)產(chǎn)出一份競(jìng)品監(jiān)測(cè)日?qǐng)?bào)內(nèi)容包括三家競(jìng)品網(wǎng)站當(dāng)天是否有更新、有哪些新變化、這些變化對(duì)我們應(yīng)該意味著什么、以及三條運(yùn)營(yíng)建議。這個(gè)任務(wù)看起來(lái)不復(fù)雜但前提是每天自動(dòng)跑任何一步出問(wèn)題都得知道自己怎么修復(fù)。4.1 先把任務(wù)拆解清楚用 agency-agents 搭建之前我先把這個(gè)目標(biāo)拆成了任務(wù)圖數(shù)據(jù)采集節(jié)點(diǎn) 抓取三家競(jìng)品的公開(kāi)頁(yè)以及兩個(gè)行業(yè)信息來(lái)源輸出原始 HTML 或 RSS 條目。結(jié)構(gòu)化節(jié)點(diǎn) 從原始數(shù)據(jù)里抽取時(shí)間、標(biāo)題、摘要、鏈接四要素存為news_items表。分析節(jié)點(diǎn) 基于news_items生成 DIFF 對(duì)比重點(diǎn)標(biāo)記新增關(guān)鍵詞價(jià)格變動(dòng)新上架功能。寫(xiě)作節(jié)點(diǎn) 基于分析結(jié)果生成日?qǐng)?bào)文本要求附上數(shù)據(jù)來(lái)源鏈接。交付節(jié)點(diǎn) 調(diào)用郵件接口發(fā)送給指定收件人并在內(nèi)部看板上創(chuàng)建一條任務(wù)記錄。這里沒(méi)有用線性步驟因?yàn)椴杉?jié)點(diǎn)和結(jié)構(gòu)化節(jié)點(diǎn)可以部分并行而且寫(xiě)作節(jié)點(diǎn)依賴的是分析結(jié)果而非原始數(shù)據(jù)這個(gè)依賴關(guān)系只有 DAG 才能清晰表達(dá)。規(guī)劃器在運(yùn)行時(shí)自動(dòng)生成了這張圖我只在配置里給了它數(shù)據(jù)源、產(chǎn)出物、發(fā)送對(duì)象這些邊界信息。4.2 代碼骨架三個(gè)核心類怎么協(xié)作架構(gòu)層面只寫(xiě)了三個(gè)核心類整體邏輯不算復(fù)雜class Coordinator: def __init__(self, planner, executor): self.planner planner self.executor executor def run(self, objective: str, context: dict) - RunResult: task_graph self.planner.plan(objective, context) execution_log self.executor.execute(task_graph) return RunResult(graphtask_graph, logexecution_log) class Planner: def plan(self, objective: str, context: dict) - TaskGraph: subtasks self._decompose(objective, context) dependencies self._resolve_dependencies(subtasks) return TaskGraph(subtaskssubtasks, dependenciesdependencies) class Executor: def execute(self, graph: TaskGraph) - ExecutionLog: for node in graph.ordered_nodes(): if node.is_compound() and node.needs_decomposition(): node.graph self.planner.plan(node.description, node.context) self._run_node(node) return ExecutionLog(...)注意這里的ordered_nodes()不是簡(jiǎn)單的拓?fù)渑判?。它還要考慮哪些節(jié)點(diǎn)可以并行執(zhí)行、哪些節(jié)點(diǎn)目前處于 blocked 狀態(tài)需要跳過(guò)。執(zhí)行器有一個(gè)工作線程池并行節(jié)點(diǎn)會(huì)被分到不同線程互不依賴的節(jié)點(diǎn)能同時(shí)跑。4.3 實(shí)際跑起來(lái)之后的幾個(gè)意外第一個(gè)意外是規(guī)劃器把結(jié)構(gòu)化節(jié)點(diǎn)和分析節(jié)點(diǎn)合并成了一個(gè)因?yàn)檫@兩個(gè)節(jié)點(diǎn)之間只隔了一個(gè)字段轉(zhuǎn)換模型判斷沒(méi)必要分開(kāi)。這個(gè)行為本身合理但也暴露出一個(gè)問(wèn)題模型過(guò)度合并會(huì)犧牲可觀測(cè)性。后來(lái)我加了規(guī)則要求任意兩個(gè)節(jié)點(diǎn)之間如果存在數(shù)據(jù)格式轉(zhuǎn)換或語(yǔ)義理解就必須保持獨(dú)立不能合并。第二個(gè)意外是采集節(jié)點(diǎn)反復(fù)請(qǐng)求同一個(gè)頁(yè)面因?yàn)榍耙淮握?qǐng)求超時(shí)了。原始的設(shè)計(jì)里沒(méi)有去重緩存的概念導(dǎo)致同一個(gè) URL 被請(qǐng)求了三次。后來(lái)我加了請(qǐng)求級(jí)緩存和冪等鍵同一任務(wù)內(nèi)相同參數(shù)的請(qǐng)求直接返回緩存結(jié)果成本馬上就下來(lái)了。第三個(gè)意外是寫(xiě)作節(jié)點(diǎn)生成的日?qǐng)?bào)里引用了結(jié)構(gòu)化節(jié)點(diǎn)里不存在的數(shù)據(jù)編號(hào)。這個(gè)屬于典型的模型幻覺(jué)輸入錯(cuò)位最后通過(guò)斷言工具解決寫(xiě)作節(jié)點(diǎn)在輸出日?qǐng)?bào)前會(huì)自動(dòng)檢查報(bào)告里引用的所有編號(hào)是否存在于數(shù)據(jù)源中不過(guò)斷言就重新生成最多重試兩次。實(shí)際效果很好之后幾乎沒(méi)有出現(xiàn)過(guò)無(wú)中生有的引用。5. 護(hù)欄與失敗降級(jí)讓自主系統(tǒng)不作死自主系統(tǒng)最怕的不是能力不夠而是能力夠但行為不可控。我給 agency-agents 設(shè)計(jì)護(hù)欄的時(shí)候參考的是彈幕游戲里那種保命機(jī)制你可以在邊界內(nèi)隨便操作但一旦越界系統(tǒng)立刻接管。5.1 成本上限與 Token 預(yù)算實(shí)時(shí)攔截大模型不是無(wú)限便宜的尤其一個(gè)長(zhǎng)時(shí)間運(yùn)行的多智能體系統(tǒng)一天跑下來(lái)成本控制不好真的會(huì)嚇人。我的做法是三層預(yù)算第一層是總預(yù)算這個(gè) run 最多允許消耗多少費(fèi)用超了直接停止并告知用戶任務(wù)因?yàn)轭A(yù)算不足被終止。第二層是節(jié)點(diǎn)預(yù)算每個(gè)節(jié)點(diǎn)有自己的 token 上限。比如結(jié)構(gòu)化節(jié)點(diǎn)預(yù)計(jì)最多輸入 2000 輸出 1000 token實(shí)測(cè)超過(guò) 4 倍就判定為異常觸發(fā)重規(guī)劃。第三層是實(shí)時(shí)攔截器每次調(diào)用大模型前庫(kù)里存有這個(gè)節(jié)點(diǎn)的歷史調(diào)用耗費(fèi)的計(jì)數(shù)如果本次預(yù)計(jì) token 已消耗 token 會(huì)超過(guò)節(jié)點(diǎn)預(yù)算攔截器會(huì)先拒絕這次調(diào)用而不是等到賬單出來(lái)再后悔。預(yù)算這個(gè)東西寧可設(shè)小一點(diǎn)頻繁補(bǔ)充也不要一開(kāi)始設(shè)得很大指望模型節(jié)制。模型一點(diǎn)都不節(jié)制它只會(huì)想著完成任務(wù)不會(huì)替你心疼錢(qián)。5.2 失敗降級(jí)路徑從工具摘除到詢問(wèn)用戶一個(gè)節(jié)點(diǎn)失敗了怎么辦很多框架的做法是重試 N 次然后掛掉。這個(gè)體驗(yàn)很糟糕尤其對(duì)于無(wú)人值守的任務(wù)。我設(shè)計(jì)了四級(jí)降級(jí)路徑重試如果是臨時(shí)錯(cuò)誤網(wǎng)絡(luò)抖動(dòng)、API 超時(shí)等待后重試上限三次。換工具如果當(dāng)前工具持續(xù)失敗嘗試用同一意圖下的其他工具替代。比如搜索工具掛了就切換到站點(diǎn)抓取工具 提取摘要的組合方案。重規(guī)劃如果工具都不可用說(shuō)明任務(wù)目標(biāo)本身有問(wèn)題規(guī)劃器基于已獲得的部分?jǐn)?shù)據(jù)重新分解任務(wù)縮小目標(biāo)范圍。詢問(wèn)用戶以上都失敗暫停任務(wù)生成一份帶上下文的問(wèn)詢請(qǐng)求等待人類決定是跳過(guò)、修改目標(biāo)還是終止。這套降級(jí)路徑救了我很多次。尤其是在外部接口不穩(wěn)定的時(shí)候任務(wù)不會(huì)直接掛掉而是自動(dòng)切換數(shù)據(jù)集繼續(xù)完成最后生成的報(bào)告里會(huì)明確標(biāo)注某數(shù)據(jù)源未更新使用了備選方案。讀者至少能知道數(shù)據(jù)里哪部分可信。5.3 逃逸檢測(cè)自循環(huán)、重復(fù)舊模式、工具調(diào)幻覺(jué)自主系統(tǒng)最容易被吐槽的問(wèn)題就是跑飛了。我在執(zhí)行器里內(nèi)置了三個(gè)逃逸檢測(cè)器循環(huán)檢測(cè)如果連續(xù)五個(gè)節(jié)點(diǎn)的輸出高度相似用向量相似度判斷判定為自循環(huán)中斷并觸發(fā)重規(guī)劃。動(dòng)作熵檢測(cè)如果一個(gè)任務(wù)在十個(gè)以上節(jié)點(diǎn)之間反復(fù)切換但整體目標(biāo)沒(méi)有任何進(jìn)展說(shuō)明規(guī)劃可能失控觸發(fā)詢問(wèn)用戶。參數(shù)幻覺(jué)檢測(cè)工具調(diào)用時(shí)如果參數(shù)與當(dāng)前上下文完全不相關(guān)比如分析金融報(bào)告時(shí)突然調(diào)用天氣查詢工具直接拒絕并記錄。這三個(gè)檢測(cè)器都是純代碼實(shí)現(xiàn)的沒(méi)有依賴額外的模型調(diào)用幾乎零成本。它們的意義在于把智能體沒(méi)問(wèn)題從一種信念變成一種可驗(yàn)證的斷言。每次任務(wù)結(jié)束時(shí)我會(huì)跑一份系統(tǒng)自檢報(bào)告列出所有節(jié)點(diǎn)是否越過(guò)護(hù)欄方便復(fù)盤(pán)。6. 關(guān)于什么時(shí)候不要用 Agent 方案的一點(diǎn)嘮叨做完這個(gè)項(xiàng)目之后我最大的收獲不是代碼而是一個(gè)反過(guò)來(lái)的認(rèn)知不是所有任務(wù)都適合做智能體。如果你手上是一個(gè)流程相對(duì)固定、步驟不超過(guò)五個(gè)、輸入輸出模式很明確的業(yè)務(wù)直接寫(xiě)函數(shù)編排就是最優(yōu)解——便宜、穩(wěn)定、好調(diào)試。智能體方案適合的是那些目標(biāo)明確但路徑不確定的事比如開(kāi)放性的調(diào)研、跨數(shù)據(jù)源的綜合分析、需要根據(jù)實(shí)時(shí)進(jìn)展調(diào)整方案的任務(wù)。我內(nèi)部已經(jīng)形成了一張表用來(lái)快速做技術(shù)選型判斷任務(wù)特征建議方案理由單一固定流程步驟小于 5傳統(tǒng)代碼流程便宜、可控、無(wú)需調(diào)試智能體部分節(jié)點(diǎn)需要模型理解上下文局部小模型節(jié)點(diǎn)只把語(yǔ)義理解交給模型其余保持代碼確定性路徑不固定且需要?jiǎng)討B(tài)決策智能體方案任務(wù)圖動(dòng)態(tài)生成的價(jià)值大多數(shù)據(jù)源 多角色協(xié)作智能體 消息隊(duì)列松耦合單點(diǎn)失敗不影響全局預(yù)算極其敏感毫厘必爭(zhēng)別用智能體的 token 成本開(kāi)銷(xiāo)遠(yuǎn)大于固定流程寫(xiě)到這里agency-agents 已經(jīng)穩(wěn)定跑了快兩個(gè)月我團(tuán)隊(duì)里的日?qǐng)?bào)生成行業(yè)調(diào)研數(shù)據(jù)異常復(fù)盤(pán)都遷移到了它上面。說(shuō)句實(shí)話它沒(méi)有多炫酷核心代碼甚至有點(diǎn)樸素——一個(gè)任務(wù)圖、一套決策日志、三個(gè)護(hù)欄檢測(cè)器、一個(gè)消息隊(duì)列。但正是這些樸素的東西讓自主性從一句口號(hào)變成了可以審計(jì)、可以干預(yù)、可以控制的工程事實(shí)。最后分享一個(gè)經(jīng)驗(yàn)如果你也在做類似的事先別急著寫(xiě)代碼。把你手頭最熟悉的一個(gè)業(yè)務(wù)拿下來(lái)拆成任務(wù)圖跑通一遍再考慮通用化。直接搭一個(gè)大而全的平臺(tái)大概率會(huì)卡在你根本不知道哪些抽象是必要的這個(gè)階段。從真實(shí)的業(yè)務(wù)長(zhǎng)出框架才是最有生命力的路徑。