Core Platform22 min read

Building AI Chat Applications: Streaming, Citations & RAG

Build production-ready AI chat interfaces with real-time streaming, source citations, multi-turn conversations, and entity-filtered RAG. Includes complete code examples and UI patterns.

AI chat applications—whether Q&A bots, customer support agents, or research assistants—need more than just LLM responses. They need streaming for real-time feedback, citations for trust, multi-turn memory, and RAG to ground answers in your content.

This guide covers everything from basic synchronous prompts to production streaming UIs with tool calling, entity filtering, and error handling. By the end, you'll know how to build ChatGPT-quality chat experiences powered by your own knowledge base.

What You'll Learn

  • Synchronous vs streaming conversations
  • Real-time UI patterns with streamAgent
  • Source citations and page references
  • Multi-turn conversations with context
  • Entity-filtered RAG (query by person/company)
  • Tool calling and function execution
  • Error handling and retry patterns
  • Production chat architecture

Prerequisites:

  • A Graphlit project with ingested content - Get started (15 min)
  • SDK installed: npm install graphlit-client (30 sec)
  • Basic understanding of search (RAG uses search under the hood)

Time to complete: 75 minutes
Difficulty: Intermediate

Developer Note: All Graphlit IDs are GUIDs (e.g., 550e8400-e29b-41d4-a716-446655440000). In code examples below, we use short placeholders for readability. Example outputs show realistic GUID format.


Table of Contents

  1. Basic Conversations (Synchronous)
  2. Streaming Conversations (Real-Time)
  3. Tool Calling (Function Execution)
  4. Entity-Filtered RAG
  5. Advanced Patterns
  6. Production Chat Architecture

Part 1: Basic Conversations (Synchronous)

Start with the simplest approach: ask a question, get an answer (with citations).

Your First Conversation

import { Graphlit } from 'graphlit-client';

const graphlit = new Graphlit();

// Ask a question (creates new conversation)
const response = await graphlit.promptConversation(
  'What are the key findings from the research papers?'
);

// Extract the answer
const message = response.promptConversation?.message?.message;
const conversationId = response.promptConversation?.conversation?.id;

console.log('Answer:', message);
console.log('Conversation ID:', conversationId);

Example output:

Answer: The research papers highlight three key findings: 1) Machine learning models improve accuracy by 23% when trained on domain-specific data, 2) Transfer learning reduces training time by 60%, and 3) Ensemble methods provide the most robust results across diverse datasets.

Conversation ID: 550e8400-e29b-41d4-a716-446655440000

Getting Citations (Source Attribution)

Every answer includes citations—which documents were used, page numbers, and relevance scores.

const response = await graphlit.promptConversation(
  'What are the key findings from the research papers?'
);

const message = response.promptConversation?.message?.message;
const citations = response.promptConversation?.message?.citations;

console.log('Answer:', message);
console.log('\nSources:');

citations?.forEach((citation, i) => {
  console.log(`\n[${i + 1}] ${citation.content?.name}`);
  console.log(`    Page: ${citation.startPage || 'N/A'}`);
  console.log(`    Relevance: ${(citation.score * 100).toFixed(1)}%`);
  console.log(`    Excerpt: "${citation.text?.substring(0, 100)}..."`);
});

Example output:

Answer: The research papers highlight three key findings...

Sources:

[1] Machine Learning Research Survey.pdf
    Page: 12
    Relevance: 92.3%
    Excerpt: "ML models improve accuracy by 23% when trained on domain-specific data, as demonstrated in..."

[2] Transfer Learning Applications.pdf
    Page: 8
    Relevance: 87.1%
    Excerpt: "Transfer learning reduces training time by 60% compared to training from scratch, enabling..."

[3] Ensemble Methods Study.pdf
    Page: 15
    Relevance: 83.5%
    Excerpt: "Ensemble methods combining multiple model architectures provide the most robust results..."

Why citations matter:

  • Trust: Users can verify answers
  • Exploration: Click to read source documents
  • Transparency: See what data AI used
  • Compliance: Audit trails for regulated industries

💡 Pro Tip: Citations come free with every RAG response—no extra configuration needed. The LLM automatically cites which documents it used to generate the answer.

Multi-Turn Conversations (Context Memory)

Pass the conversation ID to maintain context across turns:

const graphlit = new Graphlit();

// Turn 1
const response1 = await graphlit.promptConversation(
  'What is machine learning?'
);
const conversationId = response1.promptConversation?.conversation?.id;
console.log('Q1: What is machine learning?');
console.log('A1:', response1.promptConversation?.message?.message);

// Turn 2 (AI remembers previous context)
const response2 = await graphlit.promptConversation(
  'Can you give an example?',
  conversationId  // Same conversation = has memory
);
console.log('\nQ2: Can you give an example?');
console.log('A2:', response2.promptConversation?.message?.message);

// Turn 3
const response3 = await graphlit.promptConversation(
  'What are the limitations?',
  conversationId
);
console.log('\nQ3: What are the limitations?');
console.log('A3:', response3.promptConversation?.message?.message);

Example conversation flow:

Q1: What is machine learning?
A1: Machine learning is a subset of artificial intelligence that enables systems to learn patterns from data without explicit programming...

Q2: Can you give an example?
A2: A common example of machine learning is email spam filtering. The system learns from examples of spam vs legitimate emails...

Q3: What are the limitations?
A3: ML models have several limitations: they require large amounts of training data, can perpetuate biases in the data, and may not generalize well to scenarios significantly different from training examples...

Key insight: The AI remembers "machine learning" from Q1 when answering Q2 and Q3—no need to repeat context.

✅ Quick Win: Multi-turn conversations are enabled by default. Just pass the same conversationId to each promptConversation() call—memory handled automatically.


Part 2: Streaming Conversations (Real-Time UI)

Synchronous prompts are simple but don't provide real-time feedback. Streaming lets you display responses as they're generated—like ChatGPT.

Basic Streaming Setup

import { Graphlit } from 'graphlit-client';
import { AgentStreamEvent } from 'graphlit-client/dist/generated/graphlit-types';

const graphlit = new Graphlit();

let conversationId: string | undefined;
let currentMessage = '';

await graphlit.streamAgent(
  'Explain quantum computing',
  async (event: AgentStreamEvent) => {
    switch (event.type) {
      case 'conversation_started':
        conversationId = event.conversationId;
        console.log('Conversation started:', conversationId);
        break;
        
      case 'message_update':
        // Append text chunk to message
        currentMessage += event.message.message;
        console.log(currentMessage);  // Update UI in real-time
        
        if (!event.isStreaming) {
          // Message complete
          console.log('\n✓ Complete');
          console.log(`Tokens: ${event.message.tokens}`);
        }
        break;
        
      case 'conversation_completed':
        console.log('Conversation completed');
        break;
        
      case 'error':
        console.error('Error:', event.error.message);
        break;
    }
  },
  conversationId  // undefined for new conversation
);

What you see (real-time):

Conversation started: 550e8400-e29b-41d4-a716-446655440000
Quantum computing
Quantum computing is
Quantum computing is a revolutionary
Quantum computing is a revolutionary approach to
Quantum computing is a revolutionary approach to computation that
[... text appears word-by-word ...]
✓ Complete
Tokens: 342

Production Streaming UI Pattern

Here's a production-ready React example:

import { useState } from 'react';
import { Graphlit } from 'graphlit-client';
import { AgentStreamEvent } from 'graphlit-client/dist/generated/graphlit-types';

function ChatInterface() {
  const [messages, setMessages] = useState<Array<{ role: string; text: string }>>([]);
  const [currentMessage, setCurrentMessage] = useState('');
  const [isTyping, setIsTyping] = useState(false);
  const [conversationId, setConversationId] = useState<string | undefined>();

  const graphlit = new Graphlit();

  async function sendMessage(prompt: string) {
    // Add user message to UI
    setMessages(prev => [...prev, { role: 'user', text: prompt }]);
    setIsTyping(true);
    setCurrentMessage('');

    await graphlit.streamAgent(
      prompt,
      async (event: AgentStreamEvent) => {
        switch (event.type) {
          case 'conversation_started':
            setConversationId(event.conversationId);
            break;
            
          case 'message_update':
            // Update streaming message in real-time
            setCurrentMessage(prev => prev + event.message.message);
            
            if (!event.isStreaming) {
              // Message complete
              const finalMessage = currentMessage + event.message.message;
              setMessages(prev => [...prev, { role: 'assistant', text: finalMessage }]);
              setCurrentMessage('');
              setIsTyping(false);
            }
            break;
            
          case 'error':
            console.error(event.error);
            setIsTyping(false);
            break;
        }
      },
      conversationId
    );
  }

  return (
    <div className="chat-interface">
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={`message ${msg.role}`}>
            {msg.text}
          </div>
        ))}
        
        {/* Show streaming message */}
        {isTyping && currentMessage && (
          <div className="message assistant streaming">
            {currentMessage}
            <span className="cursor">▋</span>
          </div>
        )}
        
        {/* Show typing indicator */}
        {isTyping && !currentMessage && (
          <div className="message assistant">
            <span className="typing-indicator">●●●</span>
          </div>
        )}
      </div>
      
      <input
        type="text"
        placeholder="Ask a question..."
        onKeyDown={(e) => {
          if (e.key === 'Enter' && e.currentTarget.value) {
            sendMessage(e.currentTarget.value);
            e.currentTarget.value = '';
          }
        }}
      />
    </div>
  );
}

UI behavior:

  1. User types "Explain quantum computing" and presses Enter
  2. User message appears instantly
  3. Typing indicator (●●●) appears
  4. Text streams in word-by-word with blinking cursor
  5. When complete, cursor disappears and message is finalized

Streaming with Citations

Citations appear in the conversation_completed event:

await graphlit.streamAgent(
  'What are the key findings?',
  async (event: AgentStreamEvent) => {
    switch (event.type) {
      case 'message_update':
        // Stream text in real-time
        updateMessageBubble(event.message.message);
        break;
        
      case 'conversation_completed':
        // Citations available at the end
        const citations = event.message.citations;
        displayCitations(citations);
        break;
    }
  }
);

function displayCitations(citations: any[]) {
  console.log('\nSources:');
  citations?.forEach((citation, i) => {
    console.log(`[${i + 1}] ${citation.content.name} (p. ${citation.startPage})`);
  });
}

Part 3: Tool Calling (Function Execution)

Let AI call functions—search databases, fetch weather, query APIs, etc.

Define Tools

import { ToolDefinitionInput } from 'graphlit-client/dist/generated/graphlit-types';

const tools: ToolDefinitionInput[] = [
  {
    name: 'get_weather',
    description: 'Get current weather for a location',
    schema: JSON.stringify({
      type: 'object',
      properties: {
        location: { type: 'string', description: 'City name' },
        units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
      },
      required: ['location']
    })
  },
  {
    name: 'search_database',
    description: 'Search internal company database',
    schema: JSON.stringify({
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' },
        table: { type: 'string', description: 'Database table' }
      },
      required: ['query', 'table']
    })
  }
];

Handle Tool Calls

// Tool implementations
async function handleToolCall(toolName: string, args: any) {
  switch (toolName) {
    case 'get_weather':
      // Call weather API
      const weather = await fetch(`https://api.weather.com/${args.location}`);
      return { temperature: 72, conditions: 'Sunny' };
      
    case 'search_database':
      // Query your database
      const results = await db.query(args.table, args.query);
      return { results: results.rows };
      
    default:
      return { error: 'Unknown tool' };
  }
}

// Stream with tools
await graphlit.streamAgent(
  'What\'s the weather in San Francisco?',
  async (event: AgentStreamEvent) => {
    switch (event.type) {
      case 'tool_update':
        console.log(`Tool: ${event.toolCall.name}`);
        console.log(`Status: ${event.status}`);
        
        if (event.status === 'executing') {
          // AI wants to call a tool
          const args = JSON.parse(event.toolCall.arguments);
          const result = await handleToolCall(event.toolCall.name, args);
          console.log(`Result: ${JSON.stringify(result)}`);
        }
        break;
        
      case 'message_update':
        console.log(event.message.message);
        break;
    }
  },
  undefined,
  undefined,  // specification
  tools       // Pass tools
);

Example conversation:

User: What's the weather in San Francisco?

[Tool call: get_weather({ location: "San Francisco" })]
[Tool result: { temperature: 72, conditions: "Sunny" }]

AI: The weather in San Francisco is currently sunny with a temperature of 72°F.

Part 4: Entity-Filtered RAG

Filter RAG context by entities—answer questions about specific people, companies, or topics.

Query by Person

import { ObservableTypes } from 'graphlit-client/dist/generated/graphlit-types';

// Find Alice's entity ID
const aliceEntity = await graphlit.queryObservables({
  filter: {
    searchText: "Alice Johnson",
    types: [ObservableTypes.Person]
  }
});

const aliceId = aliceEntity.observables?.results?.[0]?.observable.id;

// Create conversation filtered to Alice
const conversation = await graphlit.createConversation('Alice Context');
const conversationId = conversation.createConversation.id;

// Ask questions about Alice (only uses content mentioning her)
const response = await graphlit.promptConversation(
  'What projects is Alice working on?',
  conversationId,
  undefined,  // specification
  undefined,  // tools
  {
    observations: {
      observables: [{ id: aliceId }]
    }
  }  // filter
);

console.log(response.promptConversation?.message?.message);

Use cases:

  • "What did Bob say about Project Phoenix?"
  • "Find emails from the CEO about budget"
  • "Meetings mentioning Acme Corp and Q4 planning"

Query by Company

// Find company entity
const acmeEntity = await graphlit.queryObservables({
  filter: {
    searchText: "Acme Corporation",
    types: [ObservableTypes.Organization]
  }
});

const acmeId = acmeEntity.observables?.results?.[0]?.observable.id;

// Ask questions filtered to Acme Corp
const response = await graphlit.promptConversation(
  'What are our partnerships with this company?',
  conversationId,
  undefined,
  undefined,
  {
    observations: {
      observables: [{ id: acmeId }]
    }
  }
);

Query by Multiple Entities

// Find Alice and Acme Corp
const aliceId = '550e8400-e29b-41d4-a716-446655440000';
const acmeId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

// Questions about Alice AND Acme Corp
const response = await graphlit.promptConversation(
  'What did Alice discuss with Acme Corp?',
  conversationId,
  undefined,
  undefined,
  {
    observations: {
      observables: [
        { id: aliceId },
        { id: acmeId }
      ]
    }
  }
);

Part 5: Advanced Patterns

Pattern 1: Reasoning Mode (Claude Extended Thinking)

For complex queries, enable extended thinking:

await graphlit.streamAgent(
  'Analyze the pros and cons of our Q4 strategy',
  async (event: AgentStreamEvent) => {
    switch (event.type) {
      case 'reasoning_update':
        // Show Claude's thinking process (optional, can hide)
        console.log('Thinking:', event.reasoning);
        break;
        
      case 'message_update':
        console.log('Answer:', event.message.message);
        break;
    }
  },
  conversationId,
  'claude-3-5-sonnet-thinking'  // Specification with extended thinking
);

When to use:

  • Complex analysis
  • Multi-step reasoning
  • Strategy questions
  • Mathematical problems

Pattern 2: Conversation Branching

Create alternative conversation paths:

// Main conversation
const mainConvo = await graphlit.createConversation('Main');
await graphlit.promptConversation('Explain AI safety', mainConvo.createConversation.id);
await graphlit.promptConversation('What are the risks?', mainConvo.createConversation.id);

// Branch: Explore different angle
const branch = await graphlit.createConversation('Alternative Path');
await graphlit.promptConversation('Explain AI safety', branch.createConversation.id);
await graphlit.promptConversation('What are the benefits?', branch.createConversation.id);

// Now you have two conversation trees

Use case: Explore multiple research directions without losing context.

Pattern 3: Retry with Backoff

Handle errors gracefully:

async function promptWithRetry(
  prompt: string,
  conversationId?: string,
  maxRetries = 3
) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await graphlit.promptConversation(prompt, conversationId);
      return response;
    } catch (error: any) {
      if (attempt === maxRetries) throw error;
      
      const delay = Math.pow(2, attempt) * 1000;  // Exponential backoff
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
const response = await promptWithRetry('Explain quantum computing');

Pattern 4: Message History

Retrieve past conversation messages:

// Get all messages in a conversation
const messages = await graphlit.getConversationMessages(conversationId);

console.log(`Conversation has ${messages.conversation.messages.length} messages`);

messages.conversation.messages.forEach(msg => {
  console.log(`${msg.role}: ${msg.message}`);
  if (msg.citations) {
    console.log(`  Sources: ${msg.citations.length}`);
  }
});

Use case: Display conversation history in UI.

Pattern 5: Delete Conversations

Clean up old conversations:

// Delete a specific conversation
await graphlit.deleteConversation(conversationId);

// Delete all conversations (use with caution!)
await graphlit.deleteAllConversations();

Part 6: Production Chat Architecture

Pattern 1: User-Scoped Conversations

Multi-tenant chat with user isolation:

// Each user has their own conversations
async function getUserConversations(userId: string) {
  // Store user→conversation mapping in your database
  const userConvos = await db.getUserConversations(userId);
  
  return userConvos;
}

async function createUserConversation(userId: string, name: string) {
  const conversation = await graphlit.createConversation(name);
  const conversationId = conversation.createConversation.id;
  
  // Save mapping
  await db.saveUserConversation(userId, conversationId);
  
  return conversationId;
}

// Usage
const userId = await getCurrentUserId();
const conversations = await getUserConversations(userId);

Pattern 2: Conversation Metadata

Store UI state alongside conversations:

interface ConversationMetadata {
  conversationId: string;
  userId: string;
  title: string;
  lastMessage: string;
  lastUpdated: Date;
  messageCount: number;
  starred: boolean;
}

async function saveConversationMetadata(metadata: ConversationMetadata) {
  // Store in your database
  await db.conversations.upsert(metadata);
}

// After each prompt
const response = await graphlit.promptConversation(prompt, conversationId);
await saveConversationMetadata({
  conversationId,
  userId,
  title: generateTitle(prompt),  // Or use AI to generate
  lastMessage: response.promptConversation?.message?.message || '',
  lastUpdated: new Date(),
  messageCount: messageCount + 1,
  starred: false
});

Pattern 3: Rate Limiting

Prevent abuse:

import { RateLimiter } from 'limiter';

// 10 messages per minute per user
const limiter = new RateLimiter({
  tokensPerInterval: 10,
  interval: 'minute'
});

async function rateLimitedPrompt(userId: string, prompt: string) {
  const allowed = await limiter.removeTokens(1);
  
  if (!allowed) {
    throw new Error('Rate limit exceeded. Please wait before sending more messages.');
  }
  
  return graphlit.promptConversation(prompt);
}

Pattern 4: Streaming State Management

Track streaming state across components:

import { create } from 'zustand';

interface StreamingState {
  isStreaming: boolean;
  currentMessage: string;
  conversationId?: string;
  error?: string;
  
  startStreaming: (convId: string) => void;
  appendMessage: (chunk: string) => void;
  finishStreaming: () => void;
  setError: (error: string) => void;
}

const useStreamingStore = create<StreamingState>((set) => ({
  isStreaming: false,
  currentMessage: '',
  conversationId: undefined,
  error: undefined,
  
  startStreaming: (convId) => set({
    isStreaming: true,
    currentMessage: '',
    conversationId: convId,
    error: undefined
  }),
  
  appendMessage: (chunk) => set((state) => ({
    currentMessage: state.currentMessage + chunk
  })),
  
  finishStreaming: () => set({
    isStreaming: false,
    currentMessage: ''
  }),
  
  setError: (error) => set({
    isStreaming: false,
    error
  })
}));

// In your component
const { isStreaming, currentMessage, startStreaming, appendMessage } = useStreamingStore();

await graphlit.streamAgent(
  prompt,
  async (event) => {
    switch (event.type) {
      case 'conversation_started':
        startStreaming(event.conversationId);
        break;
      case 'message_update':
        appendMessage(event.message.message);
        break;
    }
  }
);

Common Issues & Solutions

Issue: Citations Not Appearing

Problem: citations array is empty.

⚠️ Warning: Citations only appear when the LLM uses your content to answer. Generic questions like "What is 2+2?" won't have citations because they don't need your knowledge base.

Solution: Ensure content is ingested and indexed:

// Check if content is indexed
const content = await graphlit.getContent(contentId);
console.log('State:', content.content.state);  // Should be 'INDEXED'

// Wait for indexing to complete
let isDone = false;
while (!isDone) {
  const status = await graphlit.isContentDone(contentId);
  isDone = status.isContentDone.result;
  await new Promise(r => setTimeout(r, 2000));
}

Issue: AI Doesn't Remember Context

Problem: Multi-turn conversation loses context.

Solution: Always pass the same conversationId:

// Wrong: Creating new conversation each time
await graphlit.promptConversation('Question 1');
await graphlit.promptConversation('Question 2');  // No context!

// Right: Reuse conversation ID
const response1 = await graphlit.promptConversation('Question 1');
const conversationId = response1.promptConversation?.conversation?.id;
await graphlit.promptConversation('Question 2', conversationId);  // Has context!

Issue: Streaming Stops Mid-Response

Problem: Stream terminates before completion.

Solution: Check for errors and handle timeouts:

await graphlit.streamAgent(
  prompt,
  async (event) => {
    if (event.type === 'error') {
      console.error('Stream error:', event.error);
      // Retry or show error UI
    }
  },
  conversationId,
  undefined,
  undefined,
  undefined,
  { timeout: 60000 }  // 60s timeout
);

Issue: Tool Calls Not Working

Problem: AI doesn't call defined tools.

Solution:

  1. Ensure tool descriptions are clear
  2. Use specific prompts that require tools
// Vague prompt (AI may not call tool)
'Tell me about the weather'

// Specific prompt (AI will call tool)
'What is the current weather in San Francisco?'

What's Next?

You now know how to build production AI chat applications. Next steps:

  1. Add streaming UI for real-time feedback
  2. Implement entity filtering for scoped Q&A
  3. Add tool calling for database queries or API calls
  4. Build conversation management UI (list, star, delete)
  5. Optimize for production with rate limiting and error handling

Related guides:


Complete Example: Production Chat API

Here's a production-ready chat endpoint with all patterns combined:

import { Graphlit } from 'graphlit-client';
import { AgentStreamEvent } from 'graphlit-client/dist/generated/graphlit-types';

const graphlit = new Graphlit();

interface ChatOptions {
  prompt: string;
  conversationId?: string;
  entityIds?: string[];
  tools?: any[];
  streaming?: boolean;
}

async function productionChat(options: ChatOptions) {
  const {
    prompt,
    conversationId,
    entityIds,
    tools,
    streaming = true
  } = options;

  // Build filter if entity IDs provided
  const filter = entityIds && entityIds.length > 0 ? {
    observations: {
      observables: entityIds.map(id => ({ id }))
    }
  } : undefined;

  if (streaming) {
    // Streaming response
    const messages: string[] = [];
    let finalConversationId: string | undefined;
    let citations: any[] = [];

    await graphlit.streamAgent(
      prompt,
      async (event: AgentStreamEvent) => {
        switch (event.type) {
          case 'conversation_started':
            finalConversationId = event.conversationId;
            break;
            
          case 'message_update':
            messages.push(event.message.message);
            // Stream to client via SSE/WebSocket
            sendStreamChunk(event.message.message);
            break;
            
          case 'conversation_completed':
            citations = event.message.citations || [];
            break;
            
          case 'tool_update':
            if (event.status === 'executing') {
              // Handle tool call
              const args = JSON.parse(event.toolCall.arguments);
              const result = await handleToolCall(event.toolCall.name, args);
              console.log(`Tool ${event.toolCall.name} result:`, result);
            }
            break;
            
          case 'error':
            console.error('Stream error:', event.error);
            throw new Error(event.error.message);
        }
      },
      conversationId,
      undefined,
      tools,
      undefined,
      filter
    );

    return {
      conversationId: finalConversationId,
      message: messages.join(''),
      citations,
      streaming: true
    };
  } else {
    // Synchronous response
    const response = await graphlit.promptConversation(
      prompt,
      conversationId,
      undefined,
      tools,
      filter
    );

    return {
      conversationId: response.promptConversation?.conversation?.id,
      message: response.promptConversation?.message?.message,
      citations: response.promptConversation?.message?.citations,
      streaming: false
    };
  }
}

// Example usage
const result = await productionChat({
  prompt: 'What projects is Alice working on?',
  entityIds: ['550e8400-e29b-41d4-a716-446655440000'],  // Alice's entity ID
  streaming: true
});

console.log('Answer:', result.message);
console.log('Sources:', result.citations?.length);

Happy chatting! 💬

Ready to Build with Graphlit?

Start building AI-powered applications with our API-first platform. Free tier includes 100 credits/month — no credit card required.

No credit card required • 5 minutes to first API call

Building AI Chat Applications: Streaming, Citations & RAG | Graphlit Developer Guides