
整合大型語言模型 - Clean Architecture in TypeScript
我們現在已經可以將假的對話資料傳到前端呈現出來,接下來我們繼續讓前端的對話可以實際使用 LLM(大型語言模型)產生的訊息,因此我們需要先加入一個新的 API 端點以及更新介面將對話發送到後端。
整合後端
要讓對話由後端回傳的話,我們需要更新 Hono JSX 的介面、Hono RPC 客戶端以及我們的後端實作,初期可以先寫死假資料,而不需要實際串接語言模型,先以驗證能夠正常運作為初期目標。
以 Aider 為例,先將 src/api/chat.ts
、src/controller/chat.ts
和 src/view/Chat.tsx
三個檔案設定為可編輯的狀態。
接下來使用下面的 Prompt 進行修改。
1Refactor `handleSendMessage` in `Chat.tsx` to call the backend API to get mock data from backend.
2
3## Chat View
4
5Call `sendChatMessage(id)` api to send the message.
6
7## Chat API
8
9Add a new API client `sendChatMessage` in `api/chat.ts` to call the backend API.
10
11## Chat Controller
12
13Add a new endpoint `post('/:id')` in `ChatController.ts` to handle the request, returning mock data.
14
15The response message should be
16
17```json
18{
19 "id": 1,
20 "text": "Assist reply message"
21}
22```
開啟專案在本機測試,確認後端可以正常回覆。
如果發生問題,比較容易出錯的是 src/controller/chat.ts
這個檔案,在生成時有機會產生如下的版本。
1// ...
2const routes = app.get('/:id', async (c) => {
3 // ...
4});
5app.post('/:id', async (c) => {
6 // ...
7});
在使用 Hono RPC 時預期是 Chain Method 的形式,要將 .post()
連接在 .get()
之後,Hono RPC 才能正確的偵測到對的行為,這邊可能會需要手動稍微修正。
另一個是缺少
@hono/zod-validator
的問題,因為我們在之前的範例給過這樣的用法,直接用npm install @hono/zod-validator
加入到專案即可。
AI SDK
使用 TypeScript 要整合 LLM 最簡單的方式是使用 Vercel 的 AI SDK 來進行整合,我們會使用 OpenAI 相容的 API 來進行開發,可以根據需求自行調整使用的語言模型,開發階段先使用 Ollama 在本機部署會是不錯的做法。
1npm install ai @ai-sdk/openai
安裝完畢後,我們就可以直接使用 LLM 來產生回答,AI SDK 的使用方式非常簡單。
1import { generateText } from 'ai';
2import { openai } from '@ai-sdk/openai';
3
4const { text } = await generateText({
5 model: openai('gpt-4.1-mini'),
6 prompt: 'Explain the concept of quantum entanglement.',
7});
上面這段是官方的範例,我們只需要將 openai
載入進來即可,然而實際使用時我們會希望透過 Cloudflare Worker 的環境變數設定,因此可以改為使用自訂的方式。
1import { generateText } from 'ai';
2import { createOpenAI } from '@ai-sdk/openai';
3
4const openai = createOpenAI({
5 apiKey: 'YOUR_OPENAI',
6 baseURL: 'https://api.openai.com/v1',
7});
8
9const { text } = await generateText({
10 model: openai('gpt-4.1-mini'),
11 prompt: 'Explain the concept of quantum entanglement.',
12});
這樣我們在開發時也可以根據 baseURL
來設定要使用本機的模型,還是 OpenAI 的模型進行開發跟測試。
整合模型
接下來我們稍微手動處理 src/controller/chat.ts
來使用 LLM 回覆,這部分使用 AI 來產生出錯的機率比較大,先手動寫好範例,後續再讓 AI 幫忙重構會相對穩定不少。
我們先在 .dev.vars
設定環境變數,這會讓 Cloudflare Workers 知道該產生哪些型別定義資訊給 TypeScript 參考。
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_API_KEY=ollama
OPENAI_MODEL=gemma3:27b-it-qat
運行 npm run cf-typegen
更新定義,這樣我們就可以在 Hono 的 Context 中看到這個變數。
更新 src/controller/chat.ts
的內容,將原本的 app.post('/:id', ...)
改寫成如下的版本。
1// ...
2import { createOpenAI } from '@ai-sdk/openai'
3import { generateText } from 'ai'
4
5// ...
6.post("/:id", zValidator("json", messageSchema), async (c) => {
7 const chatId = c.req.param('id');
8 const { text } = await c.req.json();
9
10 const openai = createOpenAI({
11 apiKey: c.env.OPENAI_MODEL,
12 baseURL: c.env.OPENAI_BASE_URL ? c.env.OPENAI_BASE_URL : "https://api.openai.com/v1",
13 });
14
15 const { text: replyText } = await generateText({
16 model: openai(c.env.OPENAI_MODEL),
17 prompt: text,
18 })
19
20 // In a real implementation, we would save the message
21 // For now, return mock data
22 const message = {
23 id: Date.now().toString(),
24 text: replyText,
25 };
26
27 return c.json({ message });
28});
我們將原本 AI 放置的假資料移除,改為使用 generateText()
從 LLM 產生內容,這樣一來我們的對話功能就能實際使用 LLM 來產生,接下來要先重構成 UseCase 來將商業邏輯跟底層的機制分離開來。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 連載介紹 - 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