---
title: "模擬語言模型呼叫 - Clean Architecture in TypeScript"
date: 2025-11-21T00:00:00+08:00
publishDate: 2025-11-21T00:00:00+08:00
lastmod: 2025-11-21T10:38:12+08:00
tags: ["TypeScript","Clean Architecture","架構","經驗","AI"]
series: "clean-architecture-in-ts"
toc: true
permalink: "https://blog.aotoki.me/posts/2025/11/21/clean-architecture-in-ts-mock-llm-call/"
language: "zh-tw"
---



因為 LLM 的不確定性，再加上大多數服務都會採取呼叫 API 的方式來使用，我們很難在測試中直接呼叫，比較好的做法是將 LLM 呼叫使用 Mock 方式處理。

> 如果要測試 LLM 的表現，則會使用 Evaluation（評估）的方式，並不在這系列討論的範圍中

<!--more-->

## Cloudflare Worker 的限制{#cloudflare-worker-limitation}

雖然 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 {#generate-mock-language-model}

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` 再補上測試跟對應的測試步驟，會讓成功率提高一些⋯⋯

## 平衡{#balance}

這一系列嘗試使用 AI 來輔助開發，但實際的過程中並不一定會跟撰寫文章時示範的一樣順利，或者在製作範例時就已經多次嘗試調整，提供比較容易的 Prompt 版本。

正因為 AI 的不確定性，我們需要更仔細看待「手寫」跟「生成」兩者之間的平衡，以 `LanguageModel` 作為例子，假設我們連這個介面的存在都不清楚，那麼連生成的方法都不會出現，基本閱讀原始碼尋找關鍵知識的能力還是要有。

除此之外，到這個階段的實作，會發現 AI 生成的非常多功能都是「不太一致」的狀況，可能是風格、處理的習慣，即使都可以順利運作起來，也會發現每一次 Claude Code 在尋找參考的程式碼時，似乎仍然無法使用相同的方式實現。

這也是我們需要在 AI 輔助開發時取捨的地方，一些關鍵的行為、介面設計採用手寫的方式更容易讓 AI 理解到專案採用的風格跟規範，進而產生更穩定的實作。
