musistudio/llms

repository·main·Indexed 43 days ago

https://github.com/musistudio/llms

A universal LLM API transformation server (v1.0.53) that standardizes requests and responses across providers such as Anthropic, Gemini, Deepseek, and OpenAI. It utilizes a modular transformer system to bridge provider-specific API formats with a unified internal format, supporting both standard and real-time streaming data.

Tokens
8.6K
Snippets
32
Records
36
Agent score
54%

What's inside musistudio-llms

  1. How the LLM API transformation server works

    main

    The LLM API transformation server acts as a middleware to standardize requests and responses between different LLM providers (such as Anthropic, Gemini, and Deepseek). It uses a modular transformer system to bridge the gap between provider-specific API formats and a standardized internal format.

    Data Flow

    1. Request: Incoming provider-specific requests are transformed into a unified format using a transformer, which is then processed by the server.
    2. Response: The server's unified response is transformed back into the provider's specific format. For providers supporting real-time streaming, the server handles chunked data conversion to maintain a standardized format.
  2. Implement a custom Transformer

    main

    To support a new LLM provider, you must implement a dedicated transformer class. Each transformer is responsible for mapping between the provider's specific API and the server's unified formats (UnifiedChatRequest and UnifiedChatResponse).

    A transformer must implement the following methods:

    • transformRequestIn: Converts the provider's request format to the unified format.
    • transformResponseIn: Converts the provider's response format to the unified format.
    • transformRequestOut: Converts the unified request format to the provider's format.
    • transformResponseOut: Converts the unified response format back to the provider's format.
    • endPoint: A property specifying the API endpoint for the provider (e.g., "/v1/messages" for Anthropic).
  3. Install and run the LLM API transformation server

    main

    To use this project, you can install dependencies and run the server in development or production modes.

    Installation

    Install dependencies using npm or pnpm:

    npm install
    # or
    pnpm install

    Development

    Run the server in development mode with hot-reloading (uses nodemon + tsx for src/server.ts):

    npm run dev

    Build

    Build the project for production. This outputs to dist/cjs and dist/esm:

    npm run build

    Testing

    Run the test suite:

    npm test
    npm install
    npm run dev
    npm run build
    npm test
  4. Implement a custom LLM Transformer

    main

    To extend the LLM provider capabilities, you can implement a Transformer. A transformer allows you to intercept and modify both incoming requests and outgoing responses, enabling you to map provider-specific formats to the UnifiedChatRequest format and vice versa.

    Key lifecycle methods include:

    • transformRequestIn: Modify a UnifiedChatRequest before it is sent to a specific LLMProvider.
    • transformResponseIn: Modify a provider's Response before it is processed by the system.
    • transformRequestOut: Convert a raw request object into a UnifiedChatRequest.
    • transformResponseOut: Convert a provider's Response into a standardized format.

    Transformers can also define an endPoint, a name, an auth function for handling provider-specific authentication, and a logger.

    export type Transformer = {
      transformRequestIn?: (
        request: UnifiedChatRequest,
        provider: LLMProvider,
        context: TransformerContext,
      ) => Promise<Record<string, any>>;
      transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
      transformRequestOut?: (request: any, context: TransformerContext) => Promise<UnifiedChatRequest>;
      transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
      endPoint?: string;
      name?: string;
      auth?: (request: any, provider: LLMProvider, context: TransformerContext) => Promise<any>;
      logger?: any;
    };
  5. Configure LLMProvider for multi-provider support

    main

    The LLMProvider interface defines how a specific LLM provider is configured within the system. This is used to map provider-specific details to the unified interface.

    Fields:

    • name: The name of the provider.
    • baseUrl: The API endpoint.
    • apiKey: The authentication key.
    • models: A list of supported model strings.
    • transformer: An optional mapping of model names to an array of Transformer instances used to convert data between formats.
    const myProvider: LLMProvider = {
      name: "custom-provider",
      baseUrl: "https://api.example.com",
      apiKey: "sk-xxx",
      models: ["model-a", "model-b"],
      transformer: {
        "model-a": { use: [/* Transformer instances */] }
      }
    };
  6. Configure the Server with ServerOptions

    main

    When instantiating the Server class, you can provide a ServerOptions object. This object extends FastifyServerOptions and includes an optional initialConfig field.

    Key configuration behaviors:

    • initialConfig: An AppConfig object used to seed the ConfigService.
    • logger: If not provided, it defaults to true (enabling Fastify's default logger).
    • port & host: These are extracted from the ConfigService during .start(). If not explicitly set in the config, they default to 3000 and 127.0.0.1 respectively.
    interface ServerOptions extends FastifyServerOptions {
      initialConfig?: AppConfig;
    }
  7. Define a Transformer constructor

    main

    When registering a new transformer, you should provide a constructor that follows the TransformerConstructor type. This allows the system to instantiate your transformer with optional TransformerOptions. You can also provide a static TransformerName property on the class to identify it.

    export type TransformerConstructor = {
      new (options?: TransformerOptions): Transformer;
      TransformerName?: string;
    };
  8. Access available Transformers via the default export

    main

    The @musistudio/llms package provides a central entrypoint via its default export that contains all available Transformer classes. You can import this default object to access specific transformers for different LLM providers (like Anthropic, OpenAI, Gemini, etc.) or for specific functional transformations (like reasoning, sampling, or token management).

    import transformers from './transformer';
    
    // Example: Accessing a specific provider transformer
    const transformer = new transformers.AnthropicTransformer();
    
    // Example: Accessing a functional transformer
    const maxTokenTransformer = new transformers.MaxTokenTransformer();
  9. Convert a UnifiedChatRequest to Anthropic format

    main

    Transforms a UnifiedChatRequest into an AnthropicChatRequest. This handles the conversion of system messages, message roles, and the complex Anthropic content block structure (text vs tool_use vs tool_result).

    import { convertToAnthropic } from './utils/converter';
    
    const anthropicRequest = convertToAnthropic(unifiedRequest);
  10. Use OpenrouterTransformer to normalize OpenRouter API requests and responses

    main

    The OpenrouterTransformer implements the Transformer interface to ensure compatibility between the unified LLM request/response format and the OpenRouter API.

    Request Transformation (transformRequestIn)

    • Claude Models: If the model name includes claude, it automatically converts image URLs from base64 strings into valid data URIs (e.g., data:${media_type};base64,${url}). It also removes media_type fields to match expected formats.
    • Non-Claude Models: It removes cache_control properties from messages and content items to prevent API errors. For image_url types, it ensures the URL is treated as a standard string.
    • Options: Any TransformerOptions passed during instantiation are merged into the final request object.

    Response Transformation (transformResponseOut)

    • JSON Responses: Returns a standard Response object with the parsed JSON body.
    • Streaming Responses: Handles Server-Sent Events (SSE) to normalize OpenRouter's streaming output:
      • Reasoning/Thinking: Converts OpenRouter's reasoning delta field into a standardized thinking object containing the content and a signature.
      • Tool Calls: Generates unique UUIDs for tool call IDs (call_<uuid>) to ensure consistency.
      • Finish Reasons: Adjusts finish_reason to tool_calls if tool calls are detected, or stop otherwise.
      • Error Handling: Maps OpenRouter error payloads into a standard error format in the stream.
    import { OpenrouterTransformer } from "@musistudio/llms/transformers";
    
    // Example usage within a transformer pipeline
    const transformer = new OpenrouterTransformer({
      // custom options merged into the request
    });
    
    // Transforming an incoming request
    const transformedRequest = await transformer.transformRequestIn(unifiedChatRequest);
    
    // Transforming an outgoing response (e.g., from a fetch call)
    const transformedResponse = await transformer.transformResponseOut(openRouterFetchResponse);