Skip to content

Tutorial: Using IRIS (LLM Integration)

Learn how to integrate AI capabilities into your apps using the IRIS assistant system.

Overview

This tutorial covers:

  • Adding a chat interface to your app
  • Providing data context to the LLM
  • Creating custom AI agents with DispatcherAgentInterface
  • Configuring app-level LLM hooks
  • Suggested prompts for users

Prerequisites

Basic Chat Integration

Add a chat panel to any step by including a Chat element:

from virtualitics_sdk import App, Step, Page, Section, Card
from virtualitics_sdk.llm import Chat

class ChatStep(Step):
    def run(self, flow_metadata):
        chat = Chat(
            id="assistant",
            title="AI Assistant",
            system_prompt="You are a helpful data analysis assistant."
        )

        return Page(
            title="Chat with AI",
            sections=[
                Section(
                    title="Ask Questions",
                    cards=[
                        Card(
                            title="AI Assistant",
                            content=[chat]
                        )
                    ]
                )
            ]
        )

The chat element opens in the IRIS sidebar. Users can type messages, and the LLM responds using the system prompt you provide.

Context-Aware Chat

The real power comes from giving the LLM context about your app's data. Include relevant statistics and column information in the system prompt:

class ContextualChatStep(Step):
    def run(self, flow_metadata):
        df = self._inLink.dataset.data

        system_prompt = f"""You are analyzing a sales dataset.

Dataset overview:
- Rows: {len(df)}
- Columns: {', '.join(df.columns)}

Key statistics:
- Total sales: ${df['sales'].sum():,.2f}
- Average order: ${df['sales'].mean():,.2f}
- Date range: {df['date'].min()} to {df['date'].max()}
- Top product: {df.groupby('product')['sales'].sum().idxmax()}

Answer the user's questions about this data. Be specific and
reference actual numbers from the statistics above."""

        chat = Chat(
            id="data_chat",
            title="Data Analysis Assistant",
            system_prompt=system_prompt
        )

        return Page(
            title="Data Chat",
            sections=[
                Section(title="Data", cards=[
                    Card(title="Dataset", content=[Table(data=df)])
                ]),
                Section(title="AI", cards=[
                    Card(title="Ask about this data", content=[chat])
                ])
            ]
        )

App-Level LLM Configuration

Configure LLM behavior across your entire app using hooks and default prompts.

Default Prompts

Suggest questions so users don't stare at a blank chat:

app = App(
    name="Sales Analysis",
    description="AI-powered sales analysis",
    default_prompts=[
        "What are the top-selling products?",
        "Show me sales trends over time",
        "Are there any anomalies in the data?",
        "Suggest ways to improve performance"
    ]
)

LLM Request/Response Hooks

Intercept and modify LLM requests before they're sent, and responses before they're displayed:

async def on_llm_request(request_data, link, flow_metadata):
    """Add app-specific context before sending to LLM."""
    if hasattr(link, "dataset"):
        df = link.dataset.data
        request_data["context"] = {
            "dataset_summary": df.describe().to_dict(),
            "columns": list(df.columns),
            "row_count": len(df),
        }
    return request_data


async def on_llm_response(response_data, link, flow_metadata):
    """Post-process or log LLM responses."""
    # Example: log all responses for auditing
    print(f"LLM responded: {response_data.get('content', '')[:100]}...")
    return response_data


app = App(
    name="AI-Powered Analysis",
    description="App with LLM integration",
    on_llm_request=on_llm_request,
    on_llm_response=on_llm_response,
    default_prompts=[
        "Summarize the data",
        "What patterns do you see?"
    ]
)

Custom AI Agents

For more control than a system prompt provides, implement a DispatcherAgentInterface to handle queries with custom logic.

Basic Agent

from virtualitics_sdk.llm.agent import DispatcherAgentInterface

class SalesAgent(DispatcherAgentInterface):
    """Custom agent that answers sales-related queries."""

    async def handle_query(self, query: str, context: dict) -> str:
        dataset = context.get("dataset")

        if not dataset:
            return "No sales data is available. Please run the data loading step first."

        df = dataset.data

        query_lower = query.lower()

        if "top" in query_lower and "product" in query_lower:
            top = df.groupby("product")["sales"].sum().nlargest(5)
            lines = [f"- **{name}**: ${val:,.2f}" for name, val in top.items()]
            return "## Top 5 Products by Revenue\n\n" + "\n".join(lines)

        if "total" in query_lower:
            total = df["sales"].sum()
            return f"**Total sales:** ${total:,.2f}"

        if "average" in query_lower or "avg" in query_lower:
            avg = df["sales"].mean()
            return f"**Average sale:** ${avg:,.2f}"

        return ("I can help with:\n"
                "- Top products\n"
                "- Total sales\n"
                "- Average sale values\n\n"
                "Try asking one of these questions.")


# Attach to app
app = App(
    name="Sales Analysis",
    description="AI-powered sales analysis",
    agent=SalesAgent()
)

Agent with External Data

Agents can call external APIs or run computations:

import aiohttp

class WeatherAgent(DispatcherAgentInterface):
    """Agent that fetches weather data."""

    async def handle_query(self, query: str, context: dict) -> str:
        if "weather" not in query.lower():
            return "I can only answer weather-related questions."

        # Extract city from query (simplified)
        city = query.split("in")[-1].strip() if "in" in query else "New York"

        async with aiohttp.ClientSession() as session:
            async with session.get(
                f"https://api.weather.example.com/current?city={city}"
            ) as resp:
                if resp.status == 200:
                    data = await resp.json()
                    return (f"**Weather in {city}:**\n"
                            f"- Temperature: {data['temp']}°F\n"
                            f"- Conditions: {data['conditions']}")
                return f"Could not fetch weather for {city}."

Stateful Agent

Agents can maintain state across a conversation:

class AnalysisAgent(DispatcherAgentInterface):
    """Agent that remembers conversation context."""

    def __init__(self):
        super().__init__()
        self.history = []

    async def handle_query(self, query: str, context: dict) -> str:
        self.history.append({"role": "user", "content": query})

        # Use history for context-aware responses
        if len(self.history) > 1:
            previous = self.history[-2]["content"]
            response = f"Following up on your question about '{previous}'...\n\n"
        else:
            response = ""

        # Process query
        result = await self._analyze(query, context)
        response += result

        self.history.append({"role": "assistant", "content": response})
        return response

    async def _analyze(self, query, context):
        # Your analysis logic here
        return "Analysis result..."

Combining Chat with Visualizations

A common pattern: show data and charts alongside the chat interface so the LLM can reference what the user sees.

class AnalysisDashboardStep(Step):
    def run(self, flow_metadata):
        df = self._inLink.data.data

        # Build chart
        fig = px.scatter(df, x="feature_1", y="feature_2",
                         color="cluster", title="Cluster Analysis")

        # Build context-aware chat
        chat = Chat(
            id="analysis_chat",
            title="Analysis Assistant",
            system_prompt=f"""The user is viewing a scatter plot of
{len(df)} data points clustered into {df['cluster'].nunique()} groups.

Cluster sizes: {df['cluster'].value_counts().to_dict()}

Help them interpret the visualization and suggest next steps."""
        )

        return Page(
            title="AI-Assisted Analysis",
            sections=[
                Section(title="Visualization", cards=[
                    Card(title="Clusters", content=[PlotlyPlot(figure=fig)])
                ]),
                Section(title="Data", cards=[
                    Card(title="Raw Data", content=[Table(data=df)])
                ]),
                Section(title="AI Assistant", cards=[
                    Card(title="Ask Questions", content=[chat])
                ])
            ]
        )

Best Practices

  1. Provide rich context: Include data statistics, column names, and relevant metadata in system prompts
  2. Set clear boundaries: Tell the LLM what it can and cannot do
  3. Suggest prompts: Help users with default_prompts — many users don't know what to ask
  4. Validate responses: Don't trust LLM outputs for critical calculations — use agents for precise answers
  5. Handle failures gracefully: The LLM may be unavailable or return unexpected responses
  6. Keep prompts focused: A system prompt about sales data shouldn't try to answer weather questions

Next Steps