release: WRNexusJS 0.4.0
This commit is contained in:
@@ -245,3 +245,5 @@ export function createAI(config: AIConfig = {}): AI {
|
||||
|
||||
return { generate, stream, streamResponse };
|
||||
}
|
||||
export { anthropicProvider, aiProvider, createAIClient } from "./providers.ts";
|
||||
export type { AIUsage, AIResult, AIProvider, AIClient, AIClientOptions } from "./providers.ts";
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
AIError,
|
||||
createAI,
|
||||
type AI,
|
||||
type AIConfig,
|
||||
type GenerateOptions,
|
||||
type Message,
|
||||
} from "./index.ts";
|
||||
|
||||
export interface AIUsage {
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
totalTokens?: number;
|
||||
}
|
||||
export interface AIResult<T = string> {
|
||||
value: T;
|
||||
provider: string;
|
||||
model?: string;
|
||||
usage?: AIUsage;
|
||||
finishReason?: string;
|
||||
raw?: unknown;
|
||||
}
|
||||
export interface AIProvider {
|
||||
name: string;
|
||||
generate(prompt: string | Message[], options?: GenerateOptions): Promise<AIResult<string>>;
|
||||
stream?(
|
||||
prompt: string | Message[],
|
||||
options?: GenerateOptions,
|
||||
): AsyncGenerator<string, void, unknown>;
|
||||
}
|
||||
export interface AIClientOptions {
|
||||
providers: AIProvider[];
|
||||
fallback?: boolean;
|
||||
onAttempt?: (provider: string, error?: unknown) => void | Promise<void>;
|
||||
}
|
||||
export interface AIClient {
|
||||
generate(
|
||||
prompt: string | Message[],
|
||||
options?: GenerateOptions & { provider?: string },
|
||||
): Promise<AIResult<string>>;
|
||||
generateObject<T>(
|
||||
prompt: string | Message[],
|
||||
options?: GenerateOptions & { provider?: string; validate?: (value: unknown) => value is T },
|
||||
): Promise<AIResult<T>>;
|
||||
stream(
|
||||
prompt: string | Message[],
|
||||
options?: GenerateOptions & { provider?: string },
|
||||
): AsyncGenerator<string, void, unknown>;
|
||||
}
|
||||
|
||||
export function anthropicProvider(config: AIConfig = {}): AIProvider {
|
||||
const client = createAI(config);
|
||||
return {
|
||||
name: "anthropic",
|
||||
async generate(prompt: string | Message[], options?: GenerateOptions) {
|
||||
return {
|
||||
value: await client.generate(prompt, options),
|
||||
provider: "anthropic",
|
||||
model: options?.model ?? config.model,
|
||||
};
|
||||
},
|
||||
stream: (prompt: string | Message[], options?: GenerateOptions) =>
|
||||
client.stream(prompt, options),
|
||||
};
|
||||
}
|
||||
|
||||
export function aiProvider(name: string, client: AI): AIProvider {
|
||||
return {
|
||||
name,
|
||||
async generate(prompt: string | Message[], options?: GenerateOptions) {
|
||||
return {
|
||||
value: await client.generate(prompt, options),
|
||||
provider: name,
|
||||
model: options?.model,
|
||||
};
|
||||
},
|
||||
stream: (prompt: string | Message[], options?: GenerateOptions) =>
|
||||
client.stream(prompt, options),
|
||||
};
|
||||
}
|
||||
|
||||
function jsonText(value: string): string {
|
||||
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(value);
|
||||
return (fenced?.[1] ?? value).trim();
|
||||
}
|
||||
|
||||
export function createAIClient(options: AIClientOptions): AIClient {
|
||||
if (!options.providers.length) throw new Error("WRN-AI-NO-PROVIDERS");
|
||||
const select = (name?: string) =>
|
||||
name ? options.providers.filter((provider) => provider.name === name) : options.providers;
|
||||
const generate: AIClient["generate"] = async (prompt, callOptions = {}) => {
|
||||
const providers = select(callOptions.provider);
|
||||
if (!providers.length)
|
||||
throw new AIError(`Unknown AI provider: ${callOptions.provider}`, 0, "provider_error");
|
||||
let last: unknown;
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
await options.onAttempt?.(provider.name);
|
||||
return await provider.generate(prompt, callOptions);
|
||||
} catch (error) {
|
||||
last = error;
|
||||
await options.onAttempt?.(provider.name, error);
|
||||
if (options.fallback === false || callOptions.provider) throw error;
|
||||
}
|
||||
}
|
||||
throw last;
|
||||
};
|
||||
return {
|
||||
generate,
|
||||
async generateObject<T>(
|
||||
prompt: string | Message[],
|
||||
callOptions: GenerateOptions & {
|
||||
provider?: string;
|
||||
validate?: (value: unknown) => value is T;
|
||||
} = {},
|
||||
) {
|
||||
const result = await generate(prompt, callOptions);
|
||||
let value: unknown;
|
||||
try {
|
||||
value = JSON.parse(jsonText(result.value));
|
||||
} catch {
|
||||
throw new AIError("AI response was not valid JSON", 0, "structured_output_error");
|
||||
}
|
||||
if (callOptions.validate && !callOptions.validate(value))
|
||||
throw new AIError(
|
||||
"AI response failed structured output validation",
|
||||
0,
|
||||
"structured_output_error",
|
||||
);
|
||||
return { ...result, value: value as T };
|
||||
},
|
||||
async *stream(prompt, callOptions = {}) {
|
||||
const providers = select(callOptions.provider);
|
||||
let last: unknown;
|
||||
for (const provider of providers) {
|
||||
if (!provider.stream) continue;
|
||||
try {
|
||||
yield* provider.stream(prompt, callOptions);
|
||||
return;
|
||||
} catch (error) {
|
||||
last = error;
|
||||
if (options.fallback === false || callOptions.provider) throw error;
|
||||
}
|
||||
}
|
||||
if (last) throw last;
|
||||
const result = await generate(prompt, callOptions);
|
||||
yield result.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user