
如果你想快速搭建一套 Agent 工作流又不想為每個語言環境分別維護 SDK那“只用 TOML 定義配置 通過 Webhook 通信”的設計會很值得參考。這個思路最早出現在 Show HN 的一條項目介紹上An agent engine with no SDK, just TOML and webhooks。它把 Agent 引擎做成了一個“配置驅動、事件驅動”的輕量層接入方不用安裝任何 SDK只需要提交一個 TOML 描述文件然后在自己的系統里暴露一個 Webhook 接收通知即可。這篇文章會圍繞這個設計理念展開先解釋 Agent Engine、TOML、Webhook 三個核心概念再說明為什么要去掉 SDK接著用一套最小可運行的 Python 示例帶你從零搭建一個“TOML 定義 Agent 工作流 Webhook 觸發與回調”的引擎雛形。文章末尾還有常見問題排查和安全建議適合想自研輕量 Agent 編排平臺或者對低代碼化 AI 工作流感興趣的同學。1. Agent Engine、TOML 與 Webhook 到底是做什么的1.1 Agent Engine把“智能體能力”變成可編排的工作流Agent Engine智能體引擎是一種運行環境負責接收一個任務拆解成步驟再調用不同類型的執行單元完成步驟最終返回結果。它和傳統函數調用最大的區別在于執行單元往往不是寫死在代碼里的而是通過配置和協議來描述的。舉個例子一個典型的 Agent 工作流可能包含接收一段用戶提問。調用大模型生成回復。判斷回復是否需要查數據庫。如果需要執行 SQL 查詢。把結果拼裝成最終答案。如果把這些步驟全部通過硬編碼寫在一個類里系統會很難擴展。Agent Engine 的思路是把這些步驟抽成可配置的工作流Workflow每個步驟可以指向一個 Agent也可以指向一個外部動作比如發送 HTTP 請求、調用內部工具。1.2 TOML一種適合描述 Agent 配置的輕量格式TOMLToms Obvious Minimal Language是一種配置文件格式設計目標是“易于閱讀、語義明確、能無歧義地映射為哈希表”。一個最簡單的 TOML 文件長這樣name demo-agent version 1.0.0 [agent] model demo-model max_tokens 1024TOML 在 Agent Engine 中的定位是“工作流和智能體的描述語言”。它不需要用戶學習新的配置語法也不用寫代碼只需要聲明“有哪些 Agent”“每個 Agent 用什么模型”“工作流有哪些步驟”即可。相比 JSONTOML 對人和 diff 更友好相比 YAMLTOML 的縮進和類型規則更嚴格不容易踩到“同一個 key 在不同引號下類型不一致”這類坑。因此在“配置復雜、希望減少解釋成本”的場景里TOML 是一個合適的中立選擇。1.3 Webhook讓外部系統不裝 SDK 也能與引擎協作Webhook 的含義是“反向 API”。通常我們調用 API 是主動向服務器發請求而 Webhook 是服務器在某些事件發生時通過 HTTP POST 請求通知第三方。它的本質是一個回調 URL。在 Agent Engine 中Webhook 承擔了兩個關鍵職責作為引擎的輸入GitLab、GitHub、工單系統、支付系統等事件源把事件 POST 到引擎指定的 Webhook 地址觸發對應工作流。作為引擎的輸出工作流執行完成后引擎將結果 POST 回調用方提供的回調地址。這樣一來接入方只需要維護兩個 HTTP 地址不需要引入引擎的客戶端 SDK也沒有語言綁定、版本沖突、依賴升級問題。2. 為什么選擇“無 SDK”架構2.1 SDK 集成方式帶來的普遍問題SDKSoftware Development Kit本身是一個非常好的抽象它把復雜的網絡通信、序列化、簽名、失敗重試等邏輯封裝成函數。但在 Agent Engine 這類偏“平臺化”的組件里SDK 集成模式會帶來幾個容易被低估的成本語言綁定成本引擎如果只提供 Java SDK那 Python、Go、Node.js 團隊都要自己維護一個客戶端。版本同步成本SDK 與引擎核心版本的兼容關系需要嚴格管理否則經常出現“SDK 更新了啟動報 NoSuchMethodError”的問題可以參考許多 Android SDK、Vivado SDK 類工具鏈的兼容性痛點。升級推廣成本業務方不升級 SDK就拿不到新能力升級 SDK又要重新回歸測試。代碼侵入成本業務系統需要引入依賴、初始化客戶端、維護連接池使原本可以依靠配置完成的事情被迫進入了代碼層。2.2 TOML Webhook 架構的優勢讓“無 SDK 架構”成立的核心是用 TOML 描述“做什么”用 Webhook 解決“怎么通信”兩者組合起來就形成了一套事件驅動、配置驅動的輕量協議。這種架構有四個明顯優勢跨語言任何能發送 HTTP POST 請求、能解析 TOML 的語言都能接入前提是引擎沒有隱藏依賴。最小化接入成本接入方只要寫一個 TOML 文件、提供兩個 URLSDK 和客戶端庫都不需要。方便可視化編排配置本身是純文本可以被上層 UI 直接編輯和保存適合做低代碼 Agent 工作流平臺。故障邊界清晰引擎只依賴 HTTP 協議調用方可以通過重試、超時、冪等等方式控制可靠性不再受制于某個 SDK 內部的連接管理。2.3 適用場景與邊界這套設計不是萬能的它更適合以下幾類場景已有多個異構系統比如 Java 業務服務 Python AI 服務 Node.js 工單服務希望統一接入 Agent 能力。團隊希望以“配置變更”而不是“代碼發版”來調整 Agent 工作流。事件驅動型任務比如“代碼變更 - 自動 review”“工單創建 - 生成摘要 - 回填業務系統”。如果是低延遲雙向流式對話、強類型 RPC、需要復雜事務補償的場景那純 Webhook TOML 的模型就需要擴展比如疊加 WebSocket、消息隊列或注冊中心。它的價值在于簡單和通用而不是替代所有中間件。3. 環境準備與項目結構3.1 運行環境為了演示一個最小可運行的 Agent Engine我會用 Python 來實現引擎主體因為 Python 3.11 之后內置了tomllib模塊可以直接解析 TOML不需要額外安裝解析庫。# 建議環境 # Python 3.11 # 可選Flask 用于接收 Webhook pip install flask requests需要說明的是本文給出的代碼是以“演示引擎設計思想”為目的并不是某個已發布項目的源碼。實際項目中你可以用 Go、Java、Node.js 實現同樣的解析和路由邏輯思路完全一致。3.2 推薦目錄結構一個最小可運行的參考工程可以這樣組織agent-engine-demo/ ├── engine.py # 引擎核心邏輯加載配置、路由、執行工作流 ├── webhook_server.py # Webhook 接收服務接收外部事件 ├── callbacks.py # 回調客戶端向業務系統發送執行結果 ├── configs/ │ └── demo-agent.toml # Agent 與工作流配置 └── requirements.txt # Python 依賴這種結構的好處是配置、引擎邏輯、網絡入口三者分離。你可以把configs/目錄放到獨立的配置中心或 Git 倉庫后續做配置審計和版本回滾都很方便。3.3 依賴說明flask提供 Webhook 接收服務方便我們快速驗證 HTTP POST 請求。requests用于引擎執行完成后向業務系統回傳結果。內置模塊tomllib解析 TOML、hmac簽名校驗、hashlib摘要算法。如果你使用的 Python 版本低于 3.11可以安裝tomli作為兼容替代# Python 3.10 及以下 try: import tomllib except ModuleNotFoundError: import tomli as tomllib4. 核心配置與語法拆解4.1 用一個 TOML 文件描述整條 Agent 工作流下面是一份完整的demo-agent.toml你可以把它放到configs/目錄下。# 文件路徑configs/demo-agent.toml [engine] name demo-engine call_back_url https://business.example.com/callback [agent.code_reviewer] type llm model your-model-name system_prompt 你是一名資深代碼審查專家請從可讀性、安全性和性能三個角度 分析下方補丁并以 Markdown 格式輸出審查意見。 max_tokens 2048 temperature 0.3 [workflow.code_review] description 收到代碼倉庫的 Merge Request 事件后執行代碼審查 trigger { type webhook, path /webhooks/code-review, method POST } [[workflow.code_review.steps]] agent code_reviewer input {{ event.patch }} [[workflow.code_review.steps]] type webhook url {{ engine.call_back_url }} method POST payload { review_result: {{ steps[0].output }} }4.2 TOML 關鍵字段解釋這段配置分為三個部分。[engine]定義引擎級信息name引擎名稱只用于日志展示。call_back_url工作流執行完成后引擎需要把結果回調給哪個地址。這里用{{ engine.call_back_url }}引用避免在步驟里重復寫 URL。[agent.code_reviewer]定義 Agenttype llm當前 Agent 類型是大模型調用。實際項目里還可能有tool、http、human_approval等類型。model模型名稱示例中用了your-model-name占位需要替換成你實際能訪問的模型。system_prompt系統提示詞用三引號字符串保持多行格式。max_tokens/temperature調用大模型時控制輸出長度和隨機性。[workflow.code_review]定義工作流trigger表示該工作流被哪個 Webhook 事件觸發。path是引擎接收事件的 URL 路徑method指定 HTTP 方法。steps執行步驟列表使用 TOML 的數組格式按順序執行。第一步agent code_reviewer表示調用名為code_reviewer的 Agent并把{{ event.patch }}作為輸入。第二步type webhook表示向{{ engine.call_back_url }}發送結果。這里的{{ ... }}是模板語法不是 TOML 內置能力。引擎在運行時會把eventWebhook 事件體、steps步驟執行記錄、engine引擎級配置注入模板上下文再進行字符串替換。這樣配置里就可以相對自然地引用動態數據而不需要寫代碼。4.3 為什么選擇“數組 內聯表”描述步驟TOML 里表示步驟列表有兩種常用方式[[workflow.code_review.steps]] agent code_reviewer input {{ event.patch }}和[workflow.code_review.steps] 1 { agent code_reviewer, input {{ event.patch }} } 2 { type webhook, url {{ engine.call_back_url }} }第一種方式適合步驟較多、字段較多的情況讀起來像表格第二種適合快速定義一個簡單順序。推薦使用第一種因為后續每一步可能增加超時、重試、條件分支等字段二維表結構擴展性更好。5. 完整實戰案例搭建一個 Webhook 觸發的代碼審查 Agent5.1 業務場景假設你的開發團隊使用 GitLab希望在每次提交 Merge RequestMR時自動調用大模型進行代碼審查審查結果再回傳給業務系統。所有協調都通過 HTTP 完成GitLab 發送 MR 事件到引擎的/webhooks/code-review。引擎解析事件體渲染模板調用代碼審查 Agent。引擎把審查結果 POST 到業務回調地址。接下來我會一步步寫出可運行的最小實現。你可以直接復制到本地運行再根據實際場景調整。5.2 實現引擎核心邏輯文件路徑engine.pyimport json import tomllib from pathlib import Path def load_config(path: str) - dict: 讀取并解析 TOML 配置文件 with open(path, rb) as f: return tomllib.load(f) def find_workflow(config: dict, path: str, method: str): 根據 Webhook 的 path 和 method 找到對應的工作流 workflows config.get(workflow, {}) for name, wf in workflows.items(): trigger wf.get(trigger, {}) if trigger.get(path) path and trigger.get(method, POST).upper() method.upper(): return name, wf return None, None def render_template(template: str, context: dict) - str: 極簡模板渲染把 {{ key.subkey }} 替換為上下文中的值 這里只做演示實際項目建議使用 Jinja2 result template while {{ in result and }} in result: start result.find({{) end result.find(}}, start) 2 expr result[start 2:end - 2].strip() value context for part in expr.split(.): if part.isdigit(): value value[int(part)] else: value value.get(part, ) result result[:start] str(value) result[end:] return resultfind_workflow是路由映射的關鍵函數。它遍歷所有工作流的trigger如果發現path和method都匹配就把這條工作流返回給調用方。render_template是模板渲染的極簡實現僅用于理解原理生產環境建議直接使用 Jinja2避免自己處理邊界條件。繼續補充步驟執行邏輯def run_workflow(config: dict, wf_name: str, wf: dict, event: dict) - dict: 按順序執行工作流中的每一步 agents config.get(agent, {}) context { event: event, engine: config.get(engine, {}), steps: [], } for step in wf.get(steps, []): if agent in step: agent_conf agents.get(step[agent]) if not agent_conf: raise RuntimeError(fAgent not found: {step[agent]}) # 這里簡化處理實際項目會在這里調用大模型服務 # 下面用一段字符串模擬 LLM 輸出結果 prompt render_template(step.get(input, ), context) output f[模擬 LLM 輸出] 已收到輸入長度 {len(prompt)}正在生成審查意見... context[steps].append({agent: step[agent], output: output}) elif step.get(type) webhook: url render_template(step.get(url, ), context) payload_raw json.dumps(step.get(payload, {})) payload_str render_template(payload_raw, context) payload json.loads(payload_str) context[steps].append({webhook_url: url, payload: payload}) return context這段代碼演示了一個極簡的步驟執行器。真實引擎中agent步驟通常會封裝對不同模型提供方的 HTTP 調用webhook步驟會把結果 POST 給目標系統。這里的重點是執行器本身不包含任何業務代碼它只依據 TOML 一步步調度。5.3 實現 Webhook 接收服務文件路徑webhook_server.pyimport hmac import hashlib from flask import Flask, request, jsonify import engine app Flask(__name__) # 配置一個 Webhook 簽名密鑰實際生產環境應該從環境變量或密鑰管理系統讀取 WEBHOOK_SECRET your-webhook-secret app.route(/) def index(): return {status: ok} app.route(/webhooks/code-review, methods[POST]) def code_review(): body request.get_data() # 1. 校驗簽名防止偽造事件 signature request.headers.get(X-Signature, ) expected hmac.new( WEBHOOK_SECRET.encode(), body, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(signature, expected): return jsonify({error: invalid signature}), 401 # 2. 解析事件 event request.get_json(silentTrue) or {} # 3. 加載配置 config engine.load_config(configs/demo-agent.toml) # 4. 路由到工作流 wf_name, wf engine.find_workflow(config, /webhooks/code-review, POST) if not wf: return jsonify({error: workflow not found}), 404 # 5. 執行工作流 result engine.run_workflow(config, wf_name, wf, event) # 6. 在演示中直接返回執行結果生產環境可以先返回 202 再異步執行 return jsonify({workflow: wf_name, result: result}), 200 if __name__ __main__: app.run(host0.0.0.0, port8080)這個 Webhook 服務做了四件事校驗簽名、解析事件、找到對應工作流、執行工作流。需要注意演示里我直接在 HTTP 請求線程里執行了工作流這對長耗時任務不友好生產環境通常會把事件先放入消息隊列再異步執行同時立即返回202 Accepted。5.4 運行與驗證先把項目目錄準備好pip install flask requests python webhook_server.py服務啟動后另開一個終端發送測試請求curl -X POST http://127.0.0.1:8080/webhooks/code-review \ -H Content-Type: application/json \ -H X-Signature: 計算出的簽名 \ -d {patch: diff --git a/src/main.py b/src/main.py\nprint(1)}簽名可以用 Python 快速計算import hmac, hashlib body b{patch: diff --git a/src/main.py b/src/main.py\\nprint(1)} print(hmac.new(byour-webhook-secret, body, hashlib.sha256).hexdigest())把輸出值替換進X-Signature請求頭就能看到類似下面的返回{ workflow: code_review, result: { steps: [ { agent: code_reviewer, output: [模擬 LLM 輸出] 已收到輸入長度 64正在生成審查意見... } ] } }這一步跑通后你就擁有了一個“TOML 配置驅動 Webhook 觸發”的 Agent 引擎雛形。接下來可以去替換agent.code_reviewer的模型調用代碼讓它真正調用大模型。6. Webhook 接收端的安全與可靠性設計Webhook 本質上就是把一個可被外部調用的 URL 暴露到了公網或內網。一旦 URL 被惡意調用輕則白跑資源重則數據泄露或觸發危險操作。因此安全設計是不可省略的一環。6.1 簽名校驗最通用的做法是事件源在請求頭帶上簽名引擎使用共享密鑰對請求體計算 HMAC 簽名并比對兩者是否一致。expected hmac.new( WEBHOOK_SECRET.encode(), body, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(signature, expected): return jsonify({error: invalid signature}), 401使用hmac.compare_digest而不是是為了防止時序攻擊。同時要注意校驗的對象必須是原始請求體request.get_data()而不是request.get_json()重新序列化后的字符串因為序列化可能改變空格和順序導致簽名對不上。6.2 冪等處理Webhook 事件在網絡抖動時可能被事件源重發多次。如果每次收到事件都執行一次 Agent就可能重復扣費、重復回傳結果。解決方式是給每個事件設置一個唯一 ID在引擎內部保存“已處理事件 ID”列表。收到事件后先查重如果已處理則直接返回舊結果。processed_events set() if event_id in processed_events: return jsonify({status: duplicate}), 200 processed_events.add(event_id)生產環境建議把 ID 存在 Redis 或數據庫里并設置合理的過期時間。6.3 重試與超時引擎作為調用方在回調業務系統時也要考慮超時和重試。任何 HTTP 請求都可能失敗所以回調客戶端應配置連接超時比如 3 秒。讀取超時比如 15 秒。重試策略對 5xx、網絡超時等錯誤進行指數退避重試。最大重試次數比如 3 次避免無限重試。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retries Retry(total3, backoff_factor1, status_forcelist[500, 502, 503, 504]) session.mount(https://, HTTPAdapter(max_retriesretries)) session.mount(http://, HTTPAdapter(max_retriesretries))7. 常見問題與排查思路在搭建和接入這套架構時下面幾個問題最容易遇到。問題現象常見原因解決思路Webhook 請求返回 404trigger 中配置的 path 與服務器路由不一致檢查 TOML 里的trigger.path確保與 Flask 路由一致簽名校驗失敗事件源和引擎使用的密鑰不一致或簽名計算方式不一致對比雙方簽名算法、密鑰、參與簽名的字段事件能收到但工作流沒有執行find_workflow沒匹配上或步驟中 agent 名稱拼寫錯誤檢查工作流名稱、agent 名稱、method 大小寫回調業務系統超時回調地址不可達或者沒有配置超時時間使用 curl 手動測試回調地址增加超時配置重復收到同一事件事件源重試機制導致增加冪等處理保存已處理事件 IDTOML 解析報錯數組或內聯表語法錯誤使用tomllib解析異常信息定位行列事件體里的字段取不到值模板 key 寫錯或事件體結構變化打印 event 原始結構核對{{ event.xxx }}補充一個排查技巧在 Webhook 服務里加一個“調試模式”把原始請求體、簽名、命中工作流名稱都寫入日志。這樣可以快速定位是網絡層問題還是配置層問題還是引擎邏輯問題。import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(webhook) app.route(/webhooks/code-review, methods[POST]) def code_review(): body request.get_data() logger.info(receive webhook, path%s, body%s, request.path, body.decode(utf-8, errorsreplace)) ...8. 最佳實踐與工程建議8.1 TOML 配置管理把 TOML 配置當作代碼一樣管理建議做到以下四點放入 Git 倉庫通過 MR/PR 流程變更保證可審計。使用環境變量替換密鑰和 URL不要直接把生產環境地址寫在配置里。配置變更要有版本記錄至少保留最近 N 個版本方便回滾。每次加載配置后做一次 schema 校驗避免字段缺失或類型錯誤在運行階段才暴露。def validate_config(config: dict): assert engine in config, 缺少 [engine] 段 assert agent in config, 缺少 [agent] 段 for name, agent in config[agent].items(): assert type in agent, fagent {name} 缺少 type8.2 日志與可觀測性Agent 工作流相比普通接口調用更復雜。一次請求可能跨越多個步驟、多個外部系統因此建議為每次執行生成一個trace_id并從 Webhook 入口一路傳遞到后續的每一步。import uuid trace_id str(uuid.uuid4()) logger.info(workflow start, trace_id%s, workflow%s, trace_id, wf_name) # 每個步驟執行時都打印 trace_id step_index這樣排查問題時可以按 trace_id 把所有相關日志串聯起來快速定位是模型調用慢還是回調失敗。8.3 異步執行與任務隊列Webhook 請求應該快速返回耗時的 Agent 調用不適合直接阻塞在 HTTP 請求線程里。推薦的演進路徑是Webhook 服務校驗簽名解析事件后把任務寫入消息隊列Redis Stream、RabbitMQ、Kafka 等。Worker 從隊列里取出任務執行工作流。執行完成后回調業務系統。這樣既降低了 Webhook 服務的負載也避免了事件源等待太久導致超時重發。8.4 安全邊界對外暴露的 Webhook URL 必須有簽名校驗。不對外暴露引擎的管理接口配置修改走內網或運維平臺。回調地址建議做白名單限制防止引擎被用來攻擊內網其他服務SSRF。引擎在發起 HTTP 請求前應校驗目標 URL 是否在允許列表內。日志中不要打印完整密鑰、事件體中的敏感字段必要時脫敏后再記錄。9. 總結與下一步學習方向到這里你已經掌握了“Agent Engine 不依賴 SDK只用 TOML 加 Webhook”的核心設計思路也親手搭建了一個最小可運行的代碼審查 Agent?;仡櫿麠l鏈路外部系統發送 Webhook 事件引擎根據 TOML 配置路由到對應工作流工作流按步驟調用 Agent最后把結果通過 Webhook 回調給業務系統。全程沒有 SDK沒有語言綁定沒有復雜的連接管理。如果你打算把這個雛形應用到真實項目中建議按下面三步走第一步把模擬 LLM 輸出替換成真實的大模型調用跑通第一個帶真實業務價值的 Agent 場景。第二步加入消息隊列和異步執行讓 Webhook 服務可以秒回202讓工作流在后臺穩定執行。第三步完善配置校驗、事件冪等、回調白名單、Trace 日志再接入配置中心實現線上動態更新。最后留一個值得思考的方向當工作流步驟變多之后順序執行往往不夠用你可能需要支持條件分支和并行步驟。到時候可以在 TOML 中增加if字段、for_each字段也可以在引擎層引入 DAG有向無環圖來編排步驟。這會是這個“無 SDK”架構走向生產級的一個自然演進方向。如果你在接入 GitLab Webhook、配置簽名校驗或者設計 TOML 步驟結構時遇到了具體報錯歡迎在評論區把報錯信息和配置貼出來我們可以一起排查。如果這篇文章對你有幫助也可以收藏備用后面做 Agent 編排時會經常回來查。