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¶
- Completed Your First App
- Understanding of LLM Concepts
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¶
- Provide rich context: Include data statistics, column names, and relevant metadata in system prompts
- Set clear boundaries: Tell the LLM what it can and cannot do
- Suggest prompts: Help users with
default_prompts— many users don't know what to ask - Validate responses: Don't trust LLM outputs for critical calculations — use agents for precise answers
- Handle failures gracefully: The LLM may be unavailable or return unexpected responses
- Keep prompts focused: A system prompt about sales data shouldn't try to answer weather questions
Next Steps¶
- Explore the LLM API Reference
- See the Agent Reference
- View Example Apps