所有入門單元免費開放 · 課程內容持續更新中

部署、監控與成本告警

Agent 在你電腦上跑得好好的,下一步是讓它每天自動工作。這堂課把第三堂的客戶回饋小隊變成排程任務,加上記錄每一步的日誌、四道預算防線與每日成本告警,最後學會出問題時怎麼快速回滾到上一個版本。

第 13.6 堂約 16 分鐘

一路走到單元的最後一堂,辛苦了。回顧一下你手上已經有的東西:

  • 會用工具的 Agent,以及自己寫的 MCP Server(第一、二堂)
  • 能平行分析客戶回饋的主管與助手小隊(第三堂)
  • 一份隨時可以重跑的 Eval 考卷(第四堂)
  • 多層的安全防護(第五堂)

但到目前為止,它們都需要你坐在電腦前按執行。這堂課要讓它在你不在場的時候,也能安穩地工作,而且出了狀況你會知道。

上線之後的日常

上線後的循環:排程每天早上 8 點觸發,Agent 在預算上限保護下執行並寫下日誌,每日檢查花費與失敗數;超過門檻就告警,看日誌用 run_id 找原因,先回滾到上一版止血。要上線新版本時,修改後先跑 Eval,通過才上線並標記版本號

部署不是「放上去就結束」,而是一個持續觀察、調整的循環。

這張圖就是這堂課的地圖,我們從左上角開始。

選一種執行方式

部署的意思,是讓程式在某個地方、以某種方式自己跑起來。依需求不同,常見的有幾種:

方式 適合 說明
本機排程 個人或小團隊、電腦常開著 Windows 的工作排程器、macOS 和 Linux 的 cron
CI 排程 不想讓自己的電腦一直開著 例如 GitHub Actions 的定時執行,金鑰放在 Secrets
雲端主機排程 需要穩定、長期執行 租一台雲端主機或使用雲端排程服務
常駐服務 別人隨時會來用,例如聊天機器人 需要一直在線的伺服器,設計上要多考慮同時使用與每位使用者的用量上限

客戶回饋週報每天跑一次就好,排程任務是最簡單也最適合的起點。

用 cron 排程(macOS、Linux)

用 crontab -e 打開排程設定,加上這一行,代表週一到週五早上 8 點執行:

0 8 * * 1-5 cd /home/你的名字/feedback-bot && .venv/bin/python team.py >> logs/cron.log 2>&1

最後的 >> logs/cron.log 2>&1 會把所有輸出和錯誤訊息存起來,事後才看得到發生了什麼。

用工作排程器(Windows)

先在專案資料夾建立一個 run.bat:

cd /d C:\Users\你的名字\feedback-bot
.venv\Scripts\python.exe team.py >> logs\task.log 2>&1

再用 PowerShell 建立每天早上 8 點的排程:

schtasks /Create /SC DAILY /ST 08:00 /TN "FeedbackReport" /TR "C:\Users\你的名字\feedback-bot\run.bat"

也可以打開 Windows 內建的「工作排程器」用圖形介面設定,效果一樣。

寫下日誌:每一步都留紀錄

你不在場時,日誌(log) 就是你的眼睛。我們建立一個小模組 observe.py,專門負責兩件事:把每次模型呼叫記下來,以及檢查這次執行有沒有超過預算。

import json
import os
import threading
import time
import uuid
from datetime import datetime
from pathlib import Path

LOG_DIR = Path(__file__).parent / "logs"
RUN_ID = uuid.uuid4().hex[:8]  # 每次執行一個編號,用來串起同一次的所有紀錄
APP_VERSION = os.getenv("APP_VERSION", "dev")
RUN_TOKEN_BUDGET = int(os.getenv("RUN_TOKEN_BUDGET", "300000"))

_used_tokens = 0
_lock = threading.Lock()  # 助手是平行執行的,計數時要上鎖


class BudgetExceeded(Exception):
    pass


def log_event(event: str, **fields) -> None:
    """把一筆紀錄附加到今天的日誌檔,一行一筆 JSON。"""
    LOG_DIR.mkdir(exist_ok=True)
    record = {
        "time": datetime.now().isoformat(timespec="seconds"),
        "run_id": RUN_ID,
        "version": APP_VERSION,
        "event": event,
        **fields,
    }
    path = LOG_DIR / f"{datetime.now():%Y-%m-%d}.jsonl"
    with _lock, path.open("a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")


def tracked_create(client, step: str, **kwargs):
    """取代 client.messages.create:先檢查預算,呼叫後記下用量與耗時。"""
    global _used_tokens
    with _lock:
        if _used_tokens >= RUN_TOKEN_BUDGET:
            raise BudgetExceeded(f"本次執行已用 {_used_tokens} tokens,超過上限")

    start = time.monotonic()
    try:
        response = client.messages.create(**kwargs)
    except Exception as e:
        log_event("api_error", step=step, error=repr(e))
        raise

    usage = response.usage
    with _lock:
        _used_tokens += usage.input_tokens + usage.output_tokens
    log_event(
        "model_call",
        step=step,
        model=kwargs.get("model"),
        input_tokens=usage.input_tokens,
        output_tokens=usage.output_tokens,
        stop_reason=response.stop_reason,
        seconds=round(time.monotonic() - start, 2),
    )
    return response

接到第三堂的 team.py

只要改三個地方。第一,ask 多一個 step 參數,改用 tracked_create:

from observe import tracked_create, log_event


def ask(system: str, content: str, step: str) -> str:
    response = tracked_create(
        client,
        step=step,
        model=MODEL,
        max_tokens=16000,
        system=system,
        messages=[{"role": "user", "content": content}],
    )
    return "".join(b.text for b in response.content if b.type == "text")

第二,呼叫 ask 的地方標上是哪一步,例如助手寫 step=f"worker:{path.name}",主管寫 step="lead"。

第三,把報告存成檔案,並記錄整次執行的開始與結束:

if __name__ == "__main__":
    log_event("run_start")
    try:
        main()
        log_event("run_end", status="ok")
    except Exception as e:
        log_event("run_end", status="failed", error=repr(e))
        raise  # 讓程式以錯誤狀態結束,排程工具才知道失敗了

main() 最後那行 print(...) 也可以改成寫入 reports/日期.md,每天的週報就會自動存檔。

跑一次之後,打開 logs 資料夾裡今天的檔案,你會看到像這樣的紀錄:

{"time": "2026-11-07T08:00:03", "run_id": "a3f9c2e1", "version": "v3", "event": "model_call", "step": "worker:store.txt", "input_tokens": 1820, "output_tokens": 412, "seconds": 6.3, ...}

日誌和追蹤

同一個 run_id 的所有紀錄排在一起,就是這次執行的完整經過:哪位助手先完成、哪一步最慢、哪一步最花 token。這種「把一次執行的每個步驟串起來」的紀錄,業界稱為追蹤(trace)。

如果你的 Agent 有用工具,也建議在執行每個工具時呼叫 log_event("tool_call", ...),記下工具名稱和是否出錯。參數裡如果可能有個人資料,記摘要就好,不要整份寫進日誌。

等規模變大,可以改用 OpenTelemetry 這類標準,或專門的 AI 觀測平台(例如 Langfuse、LangSmith),它們能把追蹤畫成時間軸圖。但概念和你現在做的一模一樣。

預算:四道防線

四道預算防線由外而內:帳號每月上限、每日花費檢查、每次執行上限(RUN_TOKEN_BUDGET 與 MAX_STEPS)、每次呼叫的 max_tokens

內層擋住單次失控,外層擋住長期累積。

防線 擋住什麼 你已經有了嗎
每次呼叫的 max_tokens 單一回應過長 有,從 LLM API 單元就在用
每次執行上限 Agent 卡在迴圈、資料量暴增 MAX_STEPS 有了,RUN_TOKEN_BUDGET 剛剛加上
每日花費檢查 排程被設錯、一天跑了幾百次 下面來寫
帳號每月上限 以上全部失靈時的最後保險 到模型供應商的後台設定

帳號上限請到 Claude Console(或你使用的模型供應商後台)的帳務與用量相關頁面,設定每月花費上限和用量通知。這一步不用寫程式,卻是最重要的保險,請今天就去設定。

每日成本告警

建立 check_costs.py,讀今天的日誌,估算花費並檢查有沒有失敗:

import json
import sys
from datetime import date
from pathlib import Path

# 到模型供應商的價格頁面,查你用的模型「每百萬 token」的價格(美元)填進來
INPUT_PRICE_PER_M = 0.0
OUTPUT_PRICE_PER_M = 0.0
DAILY_LIMIT_USD = 1.0  # 一天最多願意花多少

log_file = Path(__file__).parent / "logs" / f"{date.today():%Y-%m-%d}.jsonl"
records = []
if log_file.exists():
    lines = log_file.read_text(encoding="utf-8").splitlines()
    records = [json.loads(line) for line in lines if line.strip()]

calls = [r for r in records if r["event"] == "model_call"]
failed = [r for r in records if r["event"] == "run_end" and r["status"] == "failed"]

input_tokens = sum(r["input_tokens"] for r in calls)
output_tokens = sum(r["output_tokens"] for r in calls)
cost = (input_tokens * INPUT_PRICE_PER_M + output_tokens * OUTPUT_PRICE_PER_M) / 1_000_000

print(f"今天呼叫 {len(calls)} 次,輸入 {input_tokens} tokens,輸出 {output_tokens} tokens")
print(f"估計花費約 {cost:.4f} 美元,失敗 {len(failed)} 次")

if INPUT_PRICE_PER_M == 0:
    print("提醒:還沒有填入價格,花費估算是 0")

if cost > DAILY_LIMIT_USD or failed:
    print("需要注意:超過每日上限或有執行失敗,請查看日誌")
    sys.exit(1)  # 以錯誤狀態結束,讓排程工具或 CI 標示失敗

把它也排進每天傍晚執行。重點在最後的 sys.exit(1):程式以錯誤狀態結束時,GitHub Actions 這類 CI 會把這次執行標示為失敗,通常也會寄通知給你;本機排程則可以在工作排程器或 logs 裡看到失敗紀錄。想收到即時通知,也可以進一步請 AI 幫你串接 Slack、Discord 或 Email。

出事時:先回滾,再修理

有一天你改了提示詞,隔天週報變得亂七八糟。這時候最重要的不是立刻找出原因,而是先讓它恢復正常,這叫回滾(rollback)。

要能回滾,前提是「上一個好的版本」還找得到:

  1. 全部放進版本控制:提示詞、工具定義、MODEL 這個模型 ID,都是程式的一部分,用 Git 管理。不要只在排程的那台電腦上直接改。
  2. 上線前先跑 Eval:第四堂的考卷通過了,才換上新版本。
  3. 替上線的版本貼標籤:例如 git tag v3,並把 APP_VERSION 設成同樣的名字,日誌裡就看得出每筆紀錄是哪個版本產生的。
  4. 出事時換回上一版:讓排程改跑 v2 那份程式,確認恢復正常後,再用 Eval 和日誌慢慢找 v3 的問題。
git tag v3          # 上線前替這個版本貼標籤
git checkout v2     # 出事時:讓工作資料夾回到 v2 的狀態

如果你還不熟 Git,可以請 Claude Code 幫你操作,並請它先說明每個指令會做什麼。

上線前的檢查清單

把這個單元學到的東西串起來,上線前逐項確認:

  • 工具說明清楚,MCP Server 來源可信、版本固定(第一、二、五堂)
  • 該拆才拆,每位助手只拿需要的工具(第三堂)
  • Eval 通過,並包含注入攻擊的考題(第四、五堂)
  • 危險動作有人工確認,或在無人值守的排程裡根本不提供這些工具(第五堂)
  • 日誌、四道預算防線、每日告警都設好了(本堂)
  • 知道怎麼回滾到上一個版本(本堂)

注意第四點:排程執行時沒有人可以按「同意」,所以無人值守的 Agent,最好只給它不需要確認的安全工具。需要寄信、刪除的流程,可以讓它先產生草稿,由你早上看過再送出。

可以這樣問 AI

這是我的 team.py 和 observe.py。請幫我加上:每次執行結束時,在 logs 裡寫一筆 run_summary,包含總 token 數、總耗時、每位助手各花多少 token。另外寫一個 weekly_report.py,讀過去 7 天的日誌,列出每天的呼叫次數、token 用量與失敗次數。不要改變現有的平行執行方式。

行動練習

這是整個單元的總結練習,把前面做過的東西變成一個真的在運作的小系統:

  1. 建立 observe.py,照著步驟改好 team.py,手動執行一次,打開今天的日誌檔,找出最花 token 的是哪一步。
  2. 把 RUN_TOKEN_BUDGET 設成很小的數字(例如 1000),再執行一次,確認程式會停下來,且日誌裡有 failed 的紀錄。記得改回來。
  3. 到模型供應商的價格頁面查好價格,填進 check_costs.py 並執行,看看今天大概花了多少。
  4. 用 cron 或工作排程器,讓 team.py 每天早上自動執行。隔天檢查 logs 和 reports 資料夾,確認它真的有跑。
  5. 到 Claude Console(或你用的供應商後台)設定每月花費上限與通知。
  6. 用 Git 替現在的版本貼上 v1 標籤。下次修改前先跑 Eval,通過再貼 v2。恭喜你,你已經擁有一個會自己工作、你也管得住的 AI 系統了。

讀完了嗎?

標記完成後,單元與專案的進度會自動更新(只存在這台裝置的瀏覽器)。

卡住了?讓 AI 當你的家教複製這段提問範本,貼到 Claude 或其他 AI 工具,再補上你卡住的地方。

留言與討論

卡關了、有心得,或發現內容需要更新?都歡迎留言。留言使用 GitHub 帳號登入。

留言區即將開放。現在可以先到 GitHub 討論區 發問,或看看 交流頁 的其他管道。