FDEAI 工程實戰課AI Engineering for FDEs
中級篇 · 第 6 章Intermediate · Chapter 6

自動測試整個機器人Testing the whole bot

機器人有了搜尋、格式檢查、工具、記憶。每改一個地方,都可能影響其他地方。這一章把入門篇 B5 的「考卷」升級成兩種測試,讓你每次修改後幾秒內就知道有沒有弄壞東西。

The bot now has search, validation, tools and memory, and changing one part can affect the others. This chapter upgrades the B5 "exam" into two kinds of test that tell you within seconds whether a change broke something.

⏱ 20 分鐘min🧪 evals.py · eval_set.jsonl · tests/

這章要加的功能What this chapter adds

  • 單元測試:用 pytest 檢查每個零件(搜尋、格式檢查、工具、迴圈上限)。
  • 評測集:18 題真實問題,逐題檢查四件事。
  • 基準:分數變差就失敗,放進 CI 自動執行。
  • Unit tests: pytest checks each part (search, validation, tools, loop limits).
  • An eval set: 18 realistic questions, each checked four ways.
  • A baseline: fail when the score drops, run automatically in CI.

兩種測試,回答兩種問題Two kinds of test, two questions

單元測試Unit tests

程式的每個零件對不對?Is each part of the code right?

結果固定(用假模型)、跑得很快、每次修改都跑。例如:捏造出處時會不會重試?工具迴圈會不會停?

Deterministic (fake model), fast, run on every change. For example: does a fabricated citation trigger a retry? Does the tool loop stop?

評測Evals

整個機器人回答得好不好?Does the whole bot answer well?

用一組真實問題逐題檢查。用假模型時檢查流程;用真模型時檢查回答品質。

Checks a set of realistic questions one by one. With the fake it checks the pipeline; with the real model it checks answer quality.

單元測試:測「會出事」的地方Unit tests: test what can go wrong

好的單元測試不是只測「正常情況會成功」,而是測那些出事時會很嚴重的地方。以下是 tests/test_intermediate.py 的幾個例子:

Good unit tests don't just check the happy path; they target what would be serious if it broke. Some examples from tests/test_intermediate.py:

labs/intermediate/tests/test_intermediate.py
def test_tools_take_no_employee_id():
    # 權限:沒有任何工具能讓 AI 指定要查誰
    for tool in TOOLS:
        assert "employee_id" not in tool["input_schema"]["properties"]


def test_retry_fixes_fake_citation():
    # 第一次故意捏造出處,確認會重試並修正
    t = HandbookBot(FakeLLM(mistakes=["fake_citation"])).ask("事假一年可以請幾天?")
    assert t.attempts == 2 and t.answer.citations == ["H03"]


class LoopingLLM:
    """永遠要求用工具的模型,用來測試迴圈上限。"""
    def create(self, system, messages, tools=None, output_schema=None):
        call = ToolCall("t1", "get_today", {})
        return Reply([...], "", [call], "tool_use", 10, 10)


def test_tool_rounds_are_capped():
    # 成本:模型一直要工具時,迴圈一定會停
    t = HandbookBot(LoopingLLM()).ask("今天幾號?")
    assert "超過" in t.error and len(t.tool_calls) == config.MAX_TOOL_ROUNDS

注意 LoopingLLM:為了測一個很少發生、但發生了很貴的情況,我們寫了一個「故意壞掉」的模型。這就是第 1 章把 LLM 包成介面的回報。

Note LoopingLLM: to test a rare but expensive situation, we wrote a deliberately broken model. That's the payoff from wrapping the LLM in an interface in chapter I1.

評測集:每題檢查四件事The eval set: four checks per question

labs/intermediate/eval_set.jsonl(節錄) (excerpt)
{"id": "wfh-synonym", "question": "一週可以在家上班幾天?", "relevant": ["H05"], "must_include": ["兩天"], "tags": ["synonym"]}
{"id": "no-parking", "question": "公司有員工停車位嗎?", "relevant": [], "expect_not_found": true, "tags": ["not-in-handbook"]}
{"id": "tool-balance", "question": "我今年還剩幾天特休?", "relevant": ["H01"], "must_include": ["4"], "expect_tool": "get_leave_balance", "tags": ["tool"]}
檢查Check內容What it verifies
搜尋Search相關段落有沒有被找到;手冊沒寫的題目,應該什麼都找不到The relevant passage was found; for not-in-handbook questions, nothing should be found
內容Content答案包含關鍵字(例如「兩天」)The answer contains the key fact (e.g. 兩天)
誠實Honesty手冊沒寫的題目,found 必須是 falseFor not-in-handbook questions, found must be false
工具Tools該用工具的題目,有沒有真的呼叫Questions that need a tool actually called it
python -m labs.intermediate.evals 的輸出(節錄)Output of python -m labs.intermediate.evals (excerpt)
✓ annual-notice    請特休要提前幾天申請?
✗ annual-synonym   年假要提前多久講?
      搜尋:找到 [],應該是 ['H01']
      內容:答案沒有「三個工作天」
✗ core-hours       最晚幾點要到公司?
      搜尋:找到 [],應該是 ['H04']
      內容:答案沒有「十點」
✓ laptop-synonym   筆電壞了可以自己拿去修嗎?
✓ no-parking       公司有員工停車位嗎?
✓ tool-balance     我今年還剩幾天特休?
...
{"cases": 18, "passed": 15, "pass_rate": 0.8333}

失敗訊息直接指出是哪一步出問題:這兩題都是「搜尋」沒找到,所以要改的是搜尋(第 3 章),不是 prompt。分開檢查每一步,才知道該修哪裡。

The failure messages point straight at the broken step: both failed at search, so the fix belongs in search (chapter I3), not the prompt. Checking each step separately tells you where to look.

基準與 CIBaseline and CI

確認結果合理後,執行 --update-baseline 把分數存成基準。之後只要分數低於基準,evals.py 就會以失敗結束,CI 會擋下這次修改。這個專案的 GitHub Actions 已經設定好,每次推送都會跑全部測試。

Once the results look right, run --update-baseline to save the score. From then on, a lower score makes evals.py exit with failure and CI blocks the change. This project's GitHub Actions already runs every test on each push.

terminal
python -m pytest labs/intermediate                    # 單元測試
python -m labs.intermediate.evals                     # 評測(假模型),比較基準
python -m labs.intermediate.evals --update-baseline   # 人確認過後才更新基準
python -m labs.intermediate.evals --real              # 真模型(要 API key、會花錢、分數會浮動)
python -m labs.intermediate.evals --ollama            # 本機開源模型(免費但較慢,見第 08 章)

假模型與真模型的分工Fake vs. real model

假模型Fake真模型Real
驗證Verifies流程:搜尋、工具、檢查、重試The pipeline: search, tools, checks, retries回答品質Answer quality
成本Cost免費Free每次都要付費Paid every run
結果Results每次相同Identical every time會浮動,要看趨勢Vary; look at trends
什麼時候跑When每次修改(CI)Every change (CI)改 prompt、換模型、上線前Prompt changes, model switches, before launch

中級篇完成了You've finished the intermediate track

下一章 I7 會把它部署成別人能用的網站。到這裡,你做出了一個完整的小型 AI 應用:會搜尋、會檢查自己的回答、會用工具、記得對話、知道花了多少錢,而且每個部分都有測試。這已經是很多公司內部工具的實際樣貌。

The next chapter, I7, deploys it as a website others can use. At this point you've built a complete small AI application: it searches, checks its own answers, uses tools, remembers the conversation, knows what it costs, and every part is tested. That's what many real internal tools actually look like.

進階篇處理的是「把這種系統放到客戶的正式環境」時會遇到的問題:

The core course tackles what happens when you put systems like this into a customer's production environment:

現在就可以做作品了You can build a portfolio piece now

你已經有能力完成作品集專案的題目 1 到 4。把這個機器人換成你自己的資料,就是一個很好的開始。

You can already tackle portfolio projects 1–4. Swapping your own data into this bot is a great start.

小測驗Quiz

1. 評測失敗訊息顯示「搜尋:找到 [],應該是 ['H04']」。你應該先改哪裡?1. An eval failure says "search: found [], expected ['H04']". Where should you look first?
分步驟檢查的好處就是能定位問題。沒進 prompt 的段落,模型看不到。
Step-by-step checks locate the problem. A passage that never reaches the prompt is invisible to the model.
2. 為什麼 CI 裡用假模型,而不是每次都用真模型跑評測?2. Why use the fake model in CI instead of the real one every time?
兩者搭配:CI 擋下程式錯誤,定期的真模型評測追蹤回答品質。
Use both: CI catches code bugs, periodic real-model evals track answer quality.