Chat API
核心的對話 API,處理所有 Agent 互動
概述
Chat API 是 Lens OS SDK 的核心端點,負責處理所有 Agent 對話。它接收用戶訊息,透過 SSE (Server-Sent Events) 串流回應,包含 Agent 思考過程、工具呼叫、最終回答。
資料流程
SSE 串流流程
Client Server LLM
│ │ │
│ POST /chat │ │
│ ───────────────────────►│ │
│ │ Request completion │
│ │ ──────────────────────►│
│ │ │
│ SSE: tool_call │◄─ Tool call needed ────│
│◄────────────────────────│ │
│ │ Execute tool │
│ │ ────────┐ │
│ │◄────────┘ │
│ SSE: tool_result │ │
│◄────────────────────────│ Return result │
│ │ ──────────────────────►│
│ SSE: text │◄─ Final response ──────│
│◄────────────────────────│ │
│ SSE: done │ │
│◄────────────────────────│ │流程說明:
- Client → Server:前端發送 POST 請求到 Chat API
- Server → LLM:SDK 將訊息發送給 LLM 模型
- tool_call:LLM 判斷需要執行工具時,發送工具呼叫事件
- Execute tool:後端執行對應的 Tool Executor
- tool_result:工具執行完成,回傳結果給 LLM
- text:LLM 生成最終回應文字
- done:對話完成
基本設定
最簡單的設定:只需兩個 API Key,直接匯出 POST。
src/app/api/lens/agent/chat/route.ts
import { createAgentHandler } from "@lens-os/sdk/server";
const handler = createAgentHandler({
apiKey: process.env.LENS_OS_API_KEY!,
openaiKey: process.env.LENS_MODEL_API_KEY!,
});
export const { POST } = handler;加入用戶驗證
需要驗證身份時,在 POST handler 中注入 userId,讓 SDK 能區分不同用戶的對話歷史與 Session。 以下以 NextAuth.js 為例,其他驗證方案只需替換取得 userId 的方式即可。
src/app/api/lens/agent/chat/route.ts
import { createAgentHandler } from "@lens-os/sdk/server";
import { auth } from "@/auth";
const handler = createAgentHandler({
apiKey: process.env.LENS_OS_API_KEY!,
openaiKey: process.env.LENS_MODEL_API_KEY!,
});
export async function POST(req: Request) {
// 1. 驗證身份
const session = await auth();
if (!session?.user) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// 2. 注入 userId(讓 SDK 能區分不同用戶的對話歷史)
const userId = session.user.id || "default-user";
const body = await req.json();
return handler.POST(
new Request(req.url, {
method: "POST",
headers: req.headers,
body: JSON.stringify({ ...body, userId }),
})
);
}Note
userId 決定 Session 的歸屬。不注入時所有用戶共享同一 Session 空間。加入自訂工具
透過 toolExecutors 掛載工具,Agent 在對話中會自動判斷何時呼叫它們。
src/app/api/lens/agent/chat/route.ts
import { createAgentHandler } from "@lens-os/sdk/server";
import type { ToolExecutorConfig } from "@lens-os/sdk";
const tools: Record<string, ToolExecutorConfig> = {
// 在這裡定義你的工具,詳見 工具執行器 文檔
};
const handler = createAgentHandler({
apiKey: process.env.LENS_OS_API_KEY!,
openaiKey: process.env.LENS_MODEL_API_KEY!,
// 掛載自訂工具,Agent 可在對話中呼叫
toolExecutors: tools,
// 選填:進階設定
model: process.env.LENS_MODEL_NAME || "gpt-4o",
maxTurns: 10,
debug: process.env.NODE_ENV === "development",
});
export const { POST } = handler;Tip
請參考 工具執行器 文檔了解如何建立工具。
Handler 設定選項
| 選項 | 類型 | 預設值 | 說明 |
|---|---|---|---|
apiKey | string | 必填 | Lens OS API Key |
openaiKey | string | 必填 | LLM API Key |
openaiBaseUrl | string | "" | LLM API Base URL |
model | string | "gpt-4o" | LLM 模型名稱 |
maxTurns | number | 10 | 最大工具呼叫輪數 |
debug | boolean | false | 開啟詳細 log |
toolExecutors | object | {} | 工具執行器 |
Request Body
前端 sendMessage 會送出以下格式的請求:
Request
interface ChatRequest {
message: string; // 用戶訊息
sessionId?: string; // Session ID(自動產生)
userId?: string; // 用戶 ID(由後端注入)
context?: {
currentUrl?: string; // 目前頁面 URL
pageState?: PageState; // 頁面狀態(視覺辨識用)
};
}Response 格式
回應為 SSE 串流,每個事件格式如下:
SSE Events
// 文字回應
{ "type": "text", "content": "讓我幫你搜尋..." }
// 工具呼叫開始
{ "type": "tool_call", "name": "product_search", "parameters": { "query": "TypeScript" } }
// 工具執行完成
{ "type": "tool_result", "name": "product_search", "result": { ... } }
// 錯誤
{ "type": "error", "error": "Something went wrong" }
// 完成
{ "type": "done" }各事件類型說明:
text:Agent 的文字回應,可能分多次傳送tool_call:Agent 開始執行某個工具tool_result:工具執行完成,包含結果error:發生錯誤done:整個對話回合完成
錯誤處理
建議加入完整的錯誤處理:
route.ts
export async function POST(req: Request) {
try {
const session = await auth();
if (!session?.user) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401 }
);
}
const body = await req.json();
// ... handler logic
} catch (error) {
console.error("[Chat API] Error:", error);
return new Response(
JSON.stringify({
error: error instanceof Error ? error.message : "Internal error",
}),
{ status: 500 }
);
}
}