Skip to main content

Handling Sources and Steps

Frontend Implementation Only

This guide describes frontend logic for parsing data returned by the API. The API handles the citation generation. Your goal is to render this data user-friendly (e.g., as clickable [1] badges and "Thinking..." steps).

Start with the Vanilla JS Demo

It's framework-agnostic and demonstrates all the core concepts (step rendering, citation parsing, source cards) in ~200 lines of code. View the demo →


Vanilla JS Demo

1. The Data Stream

The Bizora API streams responses using Server-Sent Events (SSE). You will receive different types of messages in the custom_data field of the chunks.

Key Message Types

TypeDescription
step_messageRepresents a distinct step in the AI's reasoning or action process (e.g., "Planning", "Querying Database").
source_messageContains a batch of sources used by the AI.
ai_metadataFinal metadata about the response.

2. Handling Steps (Reasoning)

Steps allow you to show the user what the AI is doing before it answers. This builds trust (e.g., showing "Searching tax records...").

Step Object Structure

{
"type": "step_message",
"title": "Federal Tax Bill",
"description": "Searching for recent amendments to Section 179..."
}

UI Recommendation

Display these as a collapsing "Thinking" section or a list of status updates above the final answer.


3. Handling Sources (Citations)

The API links text to sources using Node Annotations.

The Annotation Format

The API embeds unique Node IDs (UUIDs) in the text response:

The limit is $1,220,000 [8a4b9bf1-7940-4c49-a913-807fa1244966].

The Source Object

The source_message event provides the details for these IDs:

{
"type": "source_message",
"content": [
{
"node_id": "8a4b9bf1-7940-4c49-a913-807fa1244966",
"tool": "taxes_federal_internal_revenue_code",
"s3_file_path": "https://s3.../usc26.xml",
"text": "The aggregate cost which may be taken...",
"page_label": "1"
}
]
}

4. Frontend Extraction Logic

To render citations, you need to:

  1. Collect all sources from source_message events.
  2. Match the UUIDs in the text (using Regex) to these collected sources.
  3. Replace the UUID with a sequential number (e.g., [1]).

Example Logic (JavaScript)

function extractSources(streamData) {
const allSources = [];
const seenIds = new Set();

streamData.forEach(chunk => {
// 1. Handle Source Messages
if (chunk.custom_data?.type === 'source_message') {
const sources = chunk.custom_data.content || [];
sources.forEach(source => {
if (!seenIds.has(source.node_id)) {
seenIds.add(source.node_id);
allSources.push(source);
}
});
}
});

return allSources;
}

Rendering Markdown

The API response text returns standard Markdown. To ensure correct display (headers, lists, etc.) while keeping citations interactive:

  1. Parse Markdown to HTML first (using a library like marked).
  2. Replace Citation IDs in the resulting HTML.
// Example using 'marked' library
function renderContent(rawText, sources) {
// 1. Convert Markdown -> HTML
const htmlContent = marked.parse(rawText);

// 2. Replace [uuid] patterns with interactive buttons
return htmlContent.replace(/\[([a-f0-9\-]{36})\]/g, (match, nodeId) => {
const number = getCitationNumber(nodeId, sources);
return `<button onclick="showSource('${nodeId}')">${number}</button>`;
});
}

5. Scrolling Best Practices

Auto-scrolling during streaming can frustrate users who scroll up to read earlier content. Recommended approach: Avoid mid-stream auto-scrolling entirely.

// During streaming: NO auto-scroll - let user read freely
for (const chunk of streamData) {
renderChunk(chunk);
// Do NOT call scrollToBottom() here
}

// After streaming completes: optionally scroll once
scrollToBottom();

This respects user intent and prevents the jarring experience of being yanked to the bottom repeatedly.


Integration Examples