Files
test1/use-llm.md
2026-03-20 07:33:46 +00:00

296 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LLM 客户端使用指南
`llm/` 目录封装了一个统一的 AI 能力客户端 `SkillsClient`,支持聊天补全(非流式/流式、图片生成、视频生成、AI 搜索和语义重排。底层通过标准 `fetch` 调用远端 Skills API无需额外依赖。
## 目录结构
```
llm/
index.ts # 统一导出入口SkillsClient + 所有类型)
client.ts # SkillsClient 实现chat / image / video / aiSearch / rerank
models.ts # 所有请求/响应类型定义
app/api/
llm-client.ts # 项目内共享单例读取环境变量server-only
```
## 环境变量
`.env.local` 中添加以下配置:
| 变量名 | 说明 |
|--------|------|
| `LLM_API_KEY` | Skills API 鉴权密钥Bearer Token |
| `LLM_BASE_URL` | Skills API 服务地址(如 `https://your-api.example.com` |
## 快速开始
项目已在 `app/api/llm-client.ts` 中创建好了共享单例,**在 API Route 中直接 import 使用即可**,无需重复实例化:
```typescript
import { llmClient } from '@/app/api/llm-client'
```
如需在其他 server 端模块中单独创建实例:
```typescript
import { SkillsClient } from '@/llm'
const client = new SkillsClient({
apiKey: process.env.LLM_API_KEY!,
baseUrl: process.env.LLM_BASE_URL!,
})
```
> `llm-client.ts` 仅可在 server 端使用Next.js API Route、Server Action、Server Component不能在客户端组件中 import。
## 功能详解
### 1. 非流式聊天
```typescript
import { llmClient } from '@/app/api/llm-client'
const response = await llmClient.chat({
model: 'gpt-5.1', // 可选,默认 'gpt-5.1'
messages: [
{ role: 'system', content: '你是一个助手。' },
{ role: 'user', content: '你好!' },
],
temperature: 0.7, // 可选
maxTokens: 1024, // 可选
})
const text = response.choices[0]?.message?.content
```
### 2. 流式聊天SSE
使用 `chatStream()` 返回一个 `AsyncGenerator`,逐块处理流式响应:
```typescript
import { llmClient } from '@/app/api/llm-client'
// 在 Next.js API Route 中返回流
export async function POST(req: Request) {
const { messages } = await req.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of llmClient.chatStream({ messages })) {
const delta = chunk.choices[0]?.delta?.content ?? ''
if (delta) {
controller.enqueue(encoder.encode(`data: ${delta}\n\n`))
}
}
controller.close()
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
})
}
```
### 3. 图片生成
```typescript
const result = await llmClient.imageGenerate({
prompt: '一只在星空下奔跑的狐狸,写实风格',
model: 'Nano Banana Pro', // 可选,默认 'Nano Banana Pro'
quantityGenerated: 1, // 可选,生成张数
})
if (result.success) {
const urls = result.data?.imageList // string[]
}
```
### 4. 视频生成
视频生成分为**创建任务**和**轮询任务状态**两步:
```typescript
// 第一步:创建视频生成任务
const task = await llmClient.videoCreateTask({
prompt: '海浪拍打礁石慢动作4K',
model: 'Doubao-Seedance-1.5-pro', // 可选
parameters: {
resolution: '1080p',
duration: 5,
generateAudio: true,
},
})
const taskId = task.data?.taskId
// 第二步轮询任务状态pending / running / succeeded / failed
const result = await llmClient.videoGetTask({ taskId })
if (result.data?.status === 'succeeded') {
const videoUrl = result.data.videos?.[0]?.videoUrl
}
```
### 5. AI 搜索(非流式)
```typescript
const result = await llmClient.aiSearch({
query: 'Next.js 15 新特性',
freshness: 'week', // 可选day / week / month
})
if (result.success) {
const messages = result.data?.messages
}
```
### 6. AI 搜索(流式)
```typescript
for await (const chunk of llmClient.aiSearchStream({ query: 'TypeScript 5.5 新特性' })) {
if (chunk.isFinish) break
const content = chunk.message?.content
process.stdout.write(content ?? '')
}
```
### 7. 语义重排Rerank
对候选文档按照与查询的相关性重新排序:
```typescript
const result = await llmClient.rerank({
query: '如何提升 React 渲染性能',
documents: [
'使用 React.memo 避免不必要重渲染',
'今天天气很好',
'useMemo 和 useCallback 可以缓存计算结果',
],
topN: 2, // 可选,返回前 N 条
returnDocument: true, // 可选,是否在结果中附带原文
model: 'qwen3-vl-rerank', // 可选
})
const ranked = result.data // RerankItem[],按 relevanceScore 降序
```
## API 参考
### `ChatParams`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `messages` | `Message[]` | ✅ | 对话历史,每条含 `role``content` |
| `model` | `string` | — | 模型名称,默认 `'gpt-5.1'` |
| `stream` | `boolean` | — | 是否流式,由 `chat`/`chatStream` 自动控制 |
| `maxTokens` | `number` | — | 最大生成 token 数 |
| `temperature` | `number` | — | 采样温度02 |
| `topP` | `number` | — | 核采样概率 |
| `responseFormat` | `object` | — | 返回格式(如 `{ type: 'json_object' }` |
| `tools` | `unknown[]` | — | Function Calling 工具列表 |
| `toolChoice` | `unknown` | — | 工具调用策略 |
### `ChatResponse`
| 字段 | 类型 | 说明 |
|------|------|------|
| `choices` | `Choice[]` | 生成结果列表 |
| `choices[].message` | `ChoiceMessage` | 非流式时的完整消息 |
| `choices[].delta` | `ChoiceMessage` | 流式时的增量内容 |
| `choices[].finishReason` | `string` | 结束原因(`stop` / `tool_calls` 等) |
| `usage` | `Usage` | Token 用量统计 |
| `success` | `boolean` | 是否成功 |
| `errorMessage` | `string` | 失败时的错误信息 |
### `ImageParams`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `prompt` | `string` | ✅ | 图片描述 |
| `model` | `string` | — | 默认 `'Nano Banana Pro'` |
| `quantityGenerated` | `number` | — | 生成张数 |
| `imageUrlList` | `string[]` | — | 参考图 URL 列表 |
| `watermark` | `boolean` | — | 是否添加水印 |
### `VideoParams`
| 字段 | 类型 | 说明 |
|------|------|------|
| `prompt` | `string` | 视频描述文本 |
| `model` | `string` | 默认 `'Doubao-Seedance-1.5-pro'` |
| `taskId` | `string` | 查询任务时传入 |
| `firstFrame` | `string` | 首帧图片 URL |
| `lastFrame` | `string` | 尾帧图片 URL |
| `parameters` | `VideoParameters` | 分辨率、时长、音频等参数 |
### `AiSearchParams`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | `string` | ✅ | 搜索问题 |
| `freshness` | `string` | — | 时效过滤:`day` / `week` / `month` |
| `stream` | `boolean` | — | 由 `aiSearch`/`aiSearchStream` 自动控制 |
### `RerankParams`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | `string` | ✅ | 查询文本 |
| `documents` | `string[]` | ✅ | 候选文档列表 |
| `topN` | `number` | — | 返回前 N 条结果 |
| `returnDocument` | `boolean` | — | 结果中是否附带原始文档 |
| `model` | `string` | — | 默认 `'qwen3-vl-rerank'` |
## 在 Next.js API Route 中的完整示例
```typescript
// app/api/chat/route.ts
import { llmClient } from '@/app/api/llm-client'
import type { Message } from '@/llm'
export async function POST(req: Request) {
const { messages }: { messages: Message[] } = await req.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
try {
for await (const chunk of llmClient.chatStream({
model: 'gpt-5.1',
messages,
temperature: 0.7,
})) {
const delta = chunk.choices[0]?.delta?.content
if (delta) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content: delta })}\n\n`))
}
if (chunk.choices[0]?.finishReason === 'stop') break
}
} catch (err) {
controller.error(err)
} finally {
controller.close()
}
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
})
}
```
## 注意事项
- `SkillsClient` 所有方法均为 `async`,需在 `server` 端调用API Route、Server Action
- 流式方法(`chatStream``aiSearchStream`)返回 `AsyncGenerator`,需用 `for await...of` 消费
- 视频生成为异步任务模式,需轮询 `videoGetTask()` 直到 `status === 'succeeded'`
- API Key 通过环境变量注入,不要在客户端代码中引用 `llm-client.ts`
- 所有类型均从 `@/llm` 统一导出,无需从 `@/llm/models` 单独引入