llm-ui

repository·main·Indexed 23 days ago

https://github.com/richardgill/llm-ui

A headless React-based library optimized for rendering streamed content from Large Language Models. It provides smooth animations, robust markdown parsing, and high-performance code highlighting using Shiki. The ecosystem includes specialized packages such as @llm-ui/react for general output, @llm-ui/code for code blocks, @llm-ui/json for structured data, and @llm-ui/markdown for markdown content.

Tokens
14.6K
Snippets
35
Records
118
Agent score
82%

What's inside llm-ui

  1. Overview of llm-ui

    main

    llm-ui is a React library specifically designed for rendering Large Language Model (LLM) outputs. It focuses on providing a smooth, high-performance streaming experience for text and structured data generated by AI models.

    Key capabilities include:

    • Robust Markdown Rendering: Automatically handles and removes broken markdown syntax common in streamed LLM outputs.
    • Custom Component Integration: Allows you to map specific LLM output patterns to your own custom React components.
    • Smooth Streaming: Uses throttling to smooth out the visual pauses often seen during LLM token streaming.
    • High Performance: Renders output at native frame rates.
    • Advanced Code Highlighting: Provides code blocks for all languages using Shiki.
    • Headless Design: The library is headless, meaning it provides the logic and structure while allowing you to bring your own styles and CSS.
  2. Use @llm-ui/json for custom JSON component blocks

    main
    The @llm-ui/json package provides specialized JSON blocks designed for building custom UI components that render structured JSON data. This is part of the llm-ui ecosystem, which focuses on rendering LLM outputs as interactive or styled components.
  3. How blocks work in llm-ui

    main

    The core mechanism of llm-ui is the useLLMOutput hook, which parses a single LLM chat response into a sequence of blocks.

    To use this hook, you provide two primary parameters:

    • blocks: An array of block configurations. useLLMOutput attempts to match these configurations against the incoming LLM output stream.
    • fallbackBlock: A block configuration used for any parts of the chat response that do not match any of the provided blocks.

    For example, if you provide a codeBlock configuration and a markdownBlock as a fallback, useLLMOutput will segment the stream into alternating code and markdown blocks.

  4. Control block visibility with `defaultVisible`

    main

    The defaultVisible option determines how visibleText is generated while the LLM is streaming the JSON block:

    • defaultVisible: false: No visibleText is generated until the entire JSON block is fully parsed and valid. This prevents showing broken or partial JSON to the user.
    • defaultVisible: true: visibleText is generated incrementally as the response is parsed, allowing for a more fluid streaming experience.
  5. Configure throttling in useLLMOutput

    main

    The useLLMOutput hook accepts a throttle argument to control how the UI renders the streamed output. Throttling allows the UI to lag behind the actual LLM output to improve the visual experience.

    Benefits of Throttling

    • Smoothing: It can smooth out jittery or uneven pauses in the LLM's streamed output.
    • Visual Polish: It allows blocks to hide 'non-user' characters (like the ## syntax in a markdown header) before they are rendered, creating a cleaner transition.

    Trade-offs

    • Latency: The primary disadvantage is that the LLM output is delayed in reaching the user.

    Configuration Example

    Throttling can be configured using modes like buffer and adjusted via options such as delayMultiplier.

    <ExampleSideBySide
      client:load
      example="# H1\nHi Docs\n\n```typescript\nconsole.log('hello')\n```"
      throttle="buffer"
      options={{ delayMultiplier: 2.5 }}
    />
  6. Define custom blocks for useLLMOutput

    main

    A block object defines how a specific pattern in the LLM output is identified and rendered. It requires a component and matching logic.

    Key properties for a block:

    • component: A React component that receives blockMatch as a prop.
    • findCompleteMatch: Logic to identify when a block is fully contained within the llmOutput.
    • findPartialMatch: Logic to identify when a block has started but hasn't finished yet.
    • lookBack: A function used for smooth rendering. It allows you to modify the output or the visibleText to prevent flickering or awkward partial renders during streaming.
    const block = {
      // The component to render when the block is matched.
      component: ({ blockMatch }) => <div>{blockMatch.block.match}</div>,
    
      // The logic to find a 'complete' match
      findCompleteMatch: (llmOutput: string) => ({
        startIndex: 1,
        endIndex: 10,
        outputRaw: "some llm output"
      }),
    
      // The logic to find a 'partial' match
      findPartialMatch: (llmOutput: string) => ({
        startIndex,
        endIndex,
        outputRaw
      }),
    
      // A lookback function for smooth rendering
      lookBack: ({
        output: '【{type:"buttons",buttons:[{text:"my bu"',
        isComplete: false,
        visibleTextLengthTarget: 10,
        isStreamFinished: true
      }) => {
        return {
          output: '{type:"buttons",buttons:[{text:"my bu"}]}',
          visibleText: "my bu"
        }
      }
    }
  7. Use custom blocks for specialized LLM responses

    main
    Custom blocks allow you to instruct an LLM to reply using specific syntax patterns. These patterns can then be intercepted and rendered as custom React components in your application instead of plain text. This is useful for adding interactive elements like buttons or structured data displays to an LLM chat interface.
  8. Use `visibleKeyPaths` and `invisibleKeyPaths` for granular visibility

    main

    You can use JSONPath syntax to control the visibility of specific fields within a JSON block. This is useful for hiding metadata or showing only specific parts of a complex object during streaming.

    • visibleKeyPaths: Only the specified paths will be visible.
    • invisibleKeyPaths: Everything is visible except the specified paths.
    // Only show text inside the buttons array
    {
      defaultVisible: false,
      visibleKeyPaths: ["$.buttons[*].text"],
    }
    
    // Hide the color field in all buttons
    {
      defaultVisible: true,
      invisibleKeyPaths: ["$.buttons[*].color"],
    }