模擬語言模型呼叫 - Clean Architecture in TypeScript
因為 LLM 的不確定性,再加上大多數服務都會採取呼叫 API 的方式來使用,我們很難在測試中直接呼叫,比較好的做法是將 LLM 呼叫使用 Mock 方式處理。
如果要測試 LLM 的表現,則會使用 Evaluation(評估)的方式,並不在這系列討論的範圍中
Cloudflare Worker 的限制
雖然 AI SDK 在 TypeScript 生態系能讓我們非常容易的整合 LLM 來使用,但 Cloudflare Worker 在測試環境時有一些限制,反而讓 AI SDK 內建的測試工具無法使用。
原因在於 Cloudflare Worker 是使用自己的 Runtime 而非 Node.js 因此有一部分的 Node.js API 是無法使用的,這也是我們需要在特定應用啟用 nodejs_compact 這個選項。
很剛好的 AI SDK 的 MockLanguageModel 會實際使用 Node.js API 中的一部分功能,用來模擬一個真實的 LLM API 來提供測試環境使用,這剛好是 Cloudflare Worker 環境不支援的部分,截至 2025 年 9 月為止,雖然加入了一部分的相容,但並沒有完整的實作所有 API。
也因此,想要在 Cloudflare Worker 環境測試 AI SDK 為基礎的 AI 功能,就需要自己實作 LanguageModel 這個介面,用來替代我們原本使用 AI SDK 產生的模型(如:GPT-4.1)
產生 MockLanguageModel
AI SDK 本身已經是相當複雜、龐大的生態系,光是 LanguageModel 這個介面就有非常多方法需要實現,在過去我們透過閱讀原始碼、理解意圖再實作的方式會花費不少力氣,但這就很適合使用 Coding Agent 來協助我們
以下是一個 Prompt 的範例,用於讓 Claude Code 尋找 LanguageModelV2 介面,並且實作測試所需的必要實作
We should add `POST /chat/:id` tests to `chat.spec.ts`
## MockLanguageModel
Implement `MockLanguageModel` which implements `LanguageModel` interface from AI SDK and have `doGenerate()` to return mock action
```
import { LanguageModelV2 } from '@ai-sdk/provider'
export class MockLanguageModel implements LanguageModelV2 {
}
```
Keep `MockLanguageModel` simple with minimal implementation, use `npm exec tsc -- -p test/tsconfig.json` to verify
**IMPORTANT**: Ensure you have read the interface contract from AI SDK before implement the class, the `LanguageModelV2` from `@ai-sdk/provider` is correct do not change it if you cannot pass `tsc` check
## Remove MockAssistantService
Remove `MockAssistantService` use `MockLanguageModel` to verify assistant service is covered
## Add test case for POST /chat/:id
Use `describe('GET /chat/:id')` and `describe('POST /chat/:id')` to group tests in `chat.spec.ts`
- Test add new chat message
- Test tool use works correctly
- Use API response to verify it
After everything is ready, use `npm run test` to ensure all test is passed
實際運行後,會發現很不容易成功,即使使用了 Opus 4.1 已經相對頂尖的模型,還是有可能實作失敗,上述的 Prompt 已經是盡可能完整描述任務的版本。
這就要深入一些比較精細的 Prompt 撰寫技巧,像是我們單一任務的規模、現有實作有多少參考價值這類問題,像是我們可以考慮先區分為實作 MockLanguageModel 再補上測試跟對應的測試步驟,會讓成功率提高一些⋯⋯
平衡
這一系列嘗試使用 AI 來輔助開發,但實際的過程中並不一定會跟撰寫文章時示範的一樣順利,或者在製作範例時就已經多次嘗試調整,提供比較容易的 Prompt 版本。
正因為 AI 的不確定性,我們需要更仔細看待「手寫」跟「生成」兩者之間的平衡,以 LanguageModel 作為例子,假設我們連這個介面的存在都不清楚,那麼連生成的方法都不會出現,基本閱讀原始碼尋找關鍵知識的能力還是要有。
除此之外,到這個階段的實作,會發現 AI 生成的非常多功能都是「不太一致」的狀況,可能是風格、處理的習慣,即使都可以順利運作起來,也會發現每一次 Claude Code 在尋找參考的程式碼時,似乎仍然無法使用相同的方式實現。
這也是我們需要在 AI 輔助開發時取捨的地方,一些關鍵的行為、介面設計採用手寫的方式更容易讓 AI 理解到專案採用的風格跟規範,進而產生更穩定的實作。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 連載介紹 - Clean Architecture in TypeScript
- 目標設定 - Clean Architecture in TypeScript
- Hono 框架 - Clean Architecture in TypeScript
- tsyringe 套件 - Clean Architecture in TypeScript
- 專案設定 - Clean Architecture in TypeScript
- 介面規劃 - Clean Architecture in TypeScript
- 架構規劃 - Clean Architecture in TypeScript
- 助手對話介面 - Clean Architecture in TypeScript
- 對話紀錄 API - Clean Architecture in TypeScript
- 對話紀錄 UseCase - Clean Architecture in TypeScript
- 整合大型語言模型 - Clean Architecture in TypeScript
- 對話 UseCase - Clean Architecture in TypeScript
- 購物車側欄 - Clean Architecture in TypeScript
- 側欄 Use Case - Clean Architecture in TypeScript
- 查詢商品 - Clean Architecture in TypeScript
- 更新購物車 - Clean Architecture in TypeScript
- 對話階段 - Clean Architecture in TypeScript
- 依賴注入 - Clean Architecture in TypeScript
- 測試準備 - Clean Architecture in TypeScript
- 測試步驟 - Clean Architecture in TypeScript
- 模擬語言模型呼叫 - Clean Architecture in TypeScript