初始化模版工程

This commit is contained in:
Cloud Bot
2026-03-20 07:33:46 +00:00
commit 23717e0ecd
386 changed files with 51675 additions and 0 deletions

225
llm/client.ts Normal file
View File

@@ -0,0 +1,225 @@
import type {
AiSearchParams,
AiSearchResponse,
AiSearchResult,
ChatParams,
ChatResponse,
Choice,
ChoiceMessage,
ImageParams,
ImageResult,
RerankParams,
RerankResponse,
Usage,
VideoParams,
VideoTaskResult,
} from './models'
export class SkillsClient {
private readonly apiKey: string;
private readonly baseUrl: string;
constructor(options: { apiKey: string; baseUrl: string }) {
this.apiKey = options.apiKey;
this.baseUrl = options.baseUrl;
}
private headers(): Record<string, string> {
return {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
};
}
private url(path: string): string {
return `${this.baseUrl}/skills/v1/${path}`;
}
private async post<T>(path: string, body: unknown): Promise<T> {
const resp = await fetch(this.url(path), {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(body),
});
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
}
return resp.json() as Promise<T>;
}
// ────────── Chat ──────────
private buildChatBody(params: ChatParams): Record<string, unknown> {
const body: Record<string, unknown> = {
model: params.model ?? 'gpt-5.1',
messages: params.messages,
stream: params.stream ?? false,
};
if (params.maxTokens != null) body.maxTokens = params.maxTokens;
if (params.temperature != null) body.temperature = params.temperature;
if (params.topP != null) body.topP = params.topP;
if (params.responseFormat != null) body.responseFormat = params.responseFormat;
if (params.tools != null) body.tools = params.tools;
if (params.toolChoice != null) body.toolChoice = params.toolChoice;
return body;
}
/** 非流式聊天 */
async chat(params: ChatParams): Promise<ChatResponse> {
const resp = await fetch(this.url('chat/completions'), {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(this.buildChatBody({ ...params, stream: false })),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
return parseChatResponse(await resp.json() as Record<string, unknown>);
}
/** 流式聊天 (SSE) */
async *chatStream(params: ChatParams): AsyncGenerator<ChatResponse> {
const resp = await fetch(this.url('chat/completions'), {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(this.buildChatBody({ ...params, stream: true })),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
if (!resp.body) throw new Error('Response body is null');
yield* parseSSEStream(resp.body, parseChatResponse);
}
// ────────── Image ──────────
/** 图片生成 */
async imageGenerate(params: ImageParams): Promise<ImageResult> {
return this.post<ImageResult>('image/generate', {
model: params.model ?? 'Nano Banana Pro',
...params,
});
}
// ────────── Video ──────────
/** 创建视频生成任务 */
async videoCreateTask(params: VideoParams): Promise<VideoTaskResult> {
return this.post<VideoTaskResult>('video/tasks', {
model: params.model ?? 'Doubao-Seedance-1.5-pro',
...params,
});
}
/** 查询视频生成任务状态 */
async videoGetTask(params: Pick<VideoParams, 'taskId' | 'model'>): Promise<VideoTaskResult> {
return this.post<VideoTaskResult>('video/tasks/query', {
model: params.model ?? 'Doubao-Seedance-1.5-pro',
taskId: params.taskId,
});
}
// ────────── AI Search ──────────
/** AI 搜索(非流式) */
async aiSearch(params: AiSearchParams): Promise<AiSearchResult> {
return this.post<AiSearchResult>('aiSearch', {
model: params.model ?? 'aiSearch',
...params,
stream: false,
});
}
/** AI 搜索(流式 SSE */
async *aiSearchStream(params: AiSearchParams): AsyncGenerator<AiSearchResponse> {
const resp = await fetch(this.url('aiSearch'), {
method: 'POST',
headers: this.headers(),
body: JSON.stringify({
model: params.model ?? 'aiSearch',
...params,
stream: true,
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
if (!resp.body) throw new Error('Response body is null');
yield* parseSSEStream(resp.body, (data) => (data.data ?? data) as AiSearchResponse);
}
// ────────── Rerank ──────────
/** 重排 */
async rerank(params: RerankParams): Promise<RerankResponse> {
return this.post<RerankResponse>('rerank', {
model: params.model ?? 'qwen3-vl-rerank',
...params,
});
}
}
// ────────── helpers ──────────
async function* parseSSEStream<T>(
body: ReadableStream<Uint8Array>,
parse: (data: Record<string, unknown>) => T,
): AsyncGenerator<T> {
const decoder = new TextDecoder();
const reader = body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith('data:')) continue;
const dataStr = trimmed.slice(5).trim();
if (dataStr === '[DONE]') return;
try {
yield parse(JSON.parse(dataStr) as Record<string, unknown>);
} catch { /* skip malformed lines */ }
}
}
} finally {
reader.releaseLock();
}
}
function parseChatResponse(data: Record<string, unknown>): ChatResponse {
const rawChoices = (data.choices as Record<string, unknown>[] | undefined) ?? [];
const choices: Choice[] = rawChoices.map((c) => {
const msgData = c.message as Record<string, unknown> | undefined;
const deltaData = c.delta as Record<string, unknown> | undefined;
const toMsg = (d: Record<string, unknown>): ChoiceMessage => ({
role: d.role as string | undefined,
content: d.content as string | undefined,
reasoningContent: d.reasoningContent as string | undefined,
toolCalls: d.toolCalls as unknown[] | undefined,
});
return {
message: msgData ? toMsg(msgData) : undefined,
delta: deltaData ? toMsg(deltaData) : undefined,
finishReason: (c.finishReason ?? c.finish_reason) as string | undefined,
};
});
const usageData = data.usage as Record<string, unknown> | undefined;
const usage: Usage | undefined = usageData
? {
promptTokens: (usageData.promptTokens ?? usageData.prompt_tokens) as number | undefined,
completionTokens: (usageData.completionTokens ?? usageData.completion_tokens) as number | undefined,
totalTokens: (usageData.totalTokens ?? usageData.total_tokens) as number | undefined,
}
: undefined;
return {
id: data.id as string | undefined,
model: data.model as string | undefined,
success: data.success as boolean | undefined,
errorMessage: (data.errorMessage ?? data.error_message) as string | undefined,
choices,
usage,
};
}

29
llm/index.ts Normal file
View File

@@ -0,0 +1,29 @@
export { SkillsClient } from './client'
export type {
// Chat
Message,
ChatParams,
ChatResponse,
Choice,
ChoiceMessage,
Usage,
// Image
ImageParams,
ImageResult,
// Video
VideoParams,
VideoParameters,
VideoItem,
VideoUsage,
VideoResponse,
VideoTaskResult,
// AI Search
AiSearchParams,
AiSearchMessage,
AiSearchResponse,
AiSearchResult,
// Rerank
RerankParams,
RerankItem,
RerankResponse,
} from './models'

189
llm/models.ts Normal file
View File

@@ -0,0 +1,189 @@
// ────────── Chat ──────────
export interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
}
export interface ChatParams {
model?: string;
messages: Message[];
stream?: boolean;
maxTokens?: number;
temperature?: number;
topP?: number;
responseFormat?: Record<string, unknown>;
tools?: unknown[];
toolChoice?: unknown;
}
export interface ChoiceMessage {
role?: string;
content?: string;
reasoningContent?: string;
toolCalls?: unknown[];
}
export interface Choice {
message?: ChoiceMessage;
delta?: ChoiceMessage;
finishReason?: string;
}
export interface Usage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface ChatResponse {
id?: string;
model?: string;
success?: boolean;
errorMessage?: string;
choices: Choice[];
usage?: Usage;
}
// ────────── Image ──────────
export interface ImageParams {
model?: string;
prompt: string;
imageUrlList?: string[];
size?: string;
quantityGenerated?: number;
watermark?: boolean;
resolution?: string;
pollingWaitTime?: number;
}
export interface ImageResult {
success: boolean;
data?: {
imageType?: string;
imageList?: string[];
watermark?: boolean;
};
errorMessage?: string;
}
// ────────── Video ──────────
export interface VideoParameters {
resolution?: string;
aspectRatio?: string;
duration?: number;
frames?: number;
generateAudio?: boolean;
watermark?: boolean;
enhancePrompt?: boolean;
seed?: number;
draft?: boolean;
serviceTier?: string;
}
export interface VideoParams {
model?: string;
taskId?: string;
prompt?: string;
firstFrame?: string;
lastFrame?: string;
referenceImages?: unknown[];
audioUrl?: string;
videoUrl?: string;
parameters?: VideoParameters;
callbackUrl?: string;
}
export interface VideoItem {
videoUrl?: string;
duration?: number;
frames?: number;
fps?: number;
resolution?: string;
aspectRatio?: string;
hasAudio?: boolean;
}
export interface VideoUsage {
videoCount?: number;
totalTokens?: number;
resolution?: string;
duration?: number;
audio?: boolean;
frames?: number;
}
export interface VideoResponse {
success: boolean;
model?: string;
errorMessage?: string;
requestId?: string;
taskId?: string;
/** pending | running | succeeded | failed | cancelled | expired */
status?: string;
statusMessage?: string;
videos?: VideoItem[];
videoUsage?: VideoUsage;
}
export interface VideoTaskResult {
success: boolean;
data?: VideoResponse;
errorMessage?: string;
}
// ────────── AI Search ──────────
export interface AiSearchParams {
model?: string;
query: string;
stream?: boolean;
freshness?: string;
}
export interface AiSearchMessage {
type?: string;
contentType?: string;
content?: string;
role?: string;
}
export interface AiSearchResponse {
success: boolean;
isFinish?: boolean;
index?: number;
message?: AiSearchMessage;
messages?: AiSearchMessage[];
errorMessage?: string;
}
export interface AiSearchResult {
success: boolean;
data?: AiSearchResponse;
errorMessage?: string;
}
// ────────── Rerank ──────────
export interface RerankParams {
model?: string;
query: string;
documents: string[];
topN?: number;
returnDocument?: boolean;
}
export interface RerankItem {
relevanceScore?: number;
index?: number;
paragraph?: string;
document?: unknown;
}
export interface RerankResponse {
success: boolean;
data?: RerankItem[];
errorMessage?: string;
}