Merge pull request 'chore: Remove json markdown stuff' (#2) from jank/simple-ai-provider:main into main

Reviewed-on: #2
This commit is contained in:
2025-09-04 13:51:22 +00:00
committed by Forgejo Instance

View File

@@ -22,7 +22,7 @@ export interface AIProviderConfig {
/** /**
* Message role types supported by AI providers * Message role types supported by AI providers
*/ */
export type MessageRole = 'system' | 'user' | 'assistant'; export type MessageRole = "system" | "user" | "assistant";
/** /**
* Individual message in a conversation * Individual message in a conversation
@@ -122,13 +122,13 @@ export interface CompletionChunk {
* Error types that can occur during AI operations * Error types that can occur during AI operations
*/ */
export enum AIErrorType { export enum AIErrorType {
AUTHENTICATION = 'authentication', AUTHENTICATION = "authentication",
RATE_LIMIT = 'rate_limit', RATE_LIMIT = "rate_limit",
INVALID_REQUEST = 'invalid_request', INVALID_REQUEST = "invalid_request",
MODEL_NOT_FOUND = 'model_not_found', MODEL_NOT_FOUND = "model_not_found",
NETWORK = 'network', NETWORK = "network",
TIMEOUT = 'timeout', TIMEOUT = "timeout",
UNKNOWN = 'unknown' UNKNOWN = "unknown",
} }
/** /**
@@ -139,10 +139,10 @@ export class AIProviderError extends Error {
message: string, message: string,
public type: AIErrorType, public type: AIErrorType,
public statusCode?: number, public statusCode?: number,
public originalError?: Error public originalError?: Error,
) { ) {
super(message); super(message);
this.name = 'AIProviderError'; this.name = "AIProviderError";
} }
} }
@@ -164,18 +164,18 @@ export interface ProviderInfo {
capabilities?: Record<string, any>; capabilities?: Record<string, any>;
} }
// ============================================================================ // ============================================================================
// RESPONSE TYPE UTILITIES // RESPONSE TYPE UTILITIES
// ============================================================================ // ============================================================================
/** /**
* Creates a response type definition for structured AI outputs * Creates a response type definition for structured AI outputs
* *
* @param description - Human-readable description of the expected format * @param description - Human-readable description of the expected format
* @param example - Optional example of the expected response structure * @param example - Optional example of the expected response structure
* @param strictJson - Whether to enforce strict JSON formatting (default: true) * @param strictJson - Whether to enforce strict JSON formatting (default: true)
* @returns ResponseType object for use in completion requests * @returns ResponseType object for use in completion requests
* *
* @example * @example
* ```typescript * ```typescript
* const userType = createResponseType( * const userType = createResponseType(
@@ -192,51 +192,58 @@ export interface ProviderInfo {
export function createResponseType<T = any>( export function createResponseType<T = any>(
description: string, description: string,
example?: T, example?: T,
strictJson: boolean = true strictJson: boolean = true,
): ResponseType<T> { ): ResponseType<T> {
return { return {
description, description,
example, example,
strictJson strictJson,
}; };
} }
/** /**
* Generates a system prompt instruction for structured output based on response type * Generates a system prompt instruction for structured output based on response type
* *
* @param responseType - The response type definition * @param responseType - The response type definition
* @returns Formatted system prompt instruction * @returns Formatted system prompt instruction
*/ */
export function generateResponseTypePrompt(responseType: ResponseType): string { export function generateResponseTypePrompt(responseType: ResponseType): string {
const { typeDefinition, description, example, strictJson } = responseType; const { typeDefinition, description, example, strictJson } = responseType;
let prompt = 'You are an AI assistant that must respond with a JSON object.'; let prompt = "You are an AI assistant that must respond with a JSON object.";
if (typeDefinition) { if (typeDefinition) {
prompt += ' The JSON object must strictly adhere to the following TypeScript type definition:\n\n'; prompt +=
prompt += 'Type Definition:\n```typescript\n' + typeDefinition + '\n```\n\n'; " The JSON object must strictly adhere to the following TypeScript type definition:\n\n";
prompt +=
"Type Definition:\n```typescript\n" + typeDefinition + "\n```\n\n";
} else { } else {
prompt += '\n\n'; prompt += "\n\n";
} }
prompt += 'Description: ' + description + '\n\n'; prompt += "Description: " + description + "\n\n";
if (example) { if (example) {
prompt += 'Example of the expected JSON output:\n```json\n' + JSON.stringify(example, null, 2) + '\n```\n\n'; prompt +=
"Example of the expected JSON output:\n```json\n" +
JSON.stringify(example, null, 2) +
"\n```\n\n";
} }
if (strictJson) { if (strictJson) {
prompt += 'IMPORTANT: Your entire response must be a single, valid JSON object. Do not include any additional text, explanations, or markdown formatting before or after the JSON object.'; prompt +=
"IMPORTANT: Your entire response must be a single, valid JSON object. Do not include any additional text, explanations, or markdown formatting before or after the JSON object.";
} else { } else {
prompt += 'Your response should contain a JSON object that follows the structure defined above.'; prompt +=
"Your response should contain a JSON object that follows the structure defined above.";
} }
return prompt; return prompt;
} }
/** /**
* Parses and validates that a response matches the expected type structure * Parses and validates that a response matches the expected type structure
* *
* @param response - The response content to validate * @param response - The response content to validate
* @param responseType - The expected response type * @param responseType - The expected response type
* @returns The parsed and validated data object * @returns The parsed and validated data object
@@ -244,22 +251,24 @@ export function generateResponseTypePrompt(responseType: ResponseType): string {
*/ */
export function parseAndValidateResponseType<T = any>( export function parseAndValidateResponseType<T = any>(
response: string, response: string,
responseType: ResponseType<T> responseType: ResponseType<T>,
): T { ): T {
try { try {
// Attempt to parse the JSON response // Attempt to parse the JSON response
const parsed = JSON.parse(response); const parsed = JSON.parse(
response.replaceAll("```json", "").replaceAll("```", ""),
);
// Basic validation: ensure it's a non-null object. // Basic validation: ensure it's a non-null object.
// For more robust validation, a library like Zod or Ajv could be used // For more robust validation, a library like Zod or Ajv could be used
// based on the typeDefinition, but that's beyond the current scope. // based on the typeDefinition, but that's beyond the current scope.
if (typeof parsed !== 'object' || parsed === null) { if (typeof parsed !== "object" || parsed === null) {
throw new Error('Response must be a JSON object'); throw new Error("Response must be a JSON object");
} }
// Here you could add more sophisticated validation if needed, e.g., // Here you could add more sophisticated validation if needed, e.g.,
// checking keys against the responseType.typeDefinition // checking keys against the responseType.typeDefinition
return parsed as T; return parsed as T;
} catch (error) { } catch (error) {
// Wrap parsing/validation errors in a standardized AIProviderError // Wrap parsing/validation errors in a standardized AIProviderError
@@ -267,7 +276,7 @@ export function parseAndValidateResponseType<T = any>(
`Failed to parse or validate structured response: ${(error as Error).message}`, `Failed to parse or validate structured response: ${(error as Error).message}`,
AIErrorType.INVALID_REQUEST, AIErrorType.INVALID_REQUEST,
undefined, undefined,
error as Error error as Error,
); );
} }
} }