Skip to main content
This example demonstrates building a data analysis agent with dynamic configuration based on user roles and environment using PromptSmith’s conditional methods.

What You’ll Build

A data analysis agent that:
  • Adapts its capabilities based on user roles (admin vs regular user)
  • Changes behavior based on environment (development vs production)
  • Uses conditional tool access for security
  • Implements role-based access control

Prerequisites

npm install promptsmith-ts ai @ai-sdk/openai zod
Set up your OpenAI API key:
export OPENAI_API_KEY="your-api-key-here"

Complete Example

import { createPromptBuilder } from "promptsmith-ts/builder";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

// Mock database query function
async function executeQuery(query: string, database: string) {
  // In production, this would execute actual SQL queries
  console.log(`Executing query on ${database}: ${query}`);
  
  return {
    rows: [
      { id: 1, name: "Product A", sales: 1250, region: "North" },
      { id: 2, name: "Product B", sales: 980, region: "South" },
      { id: 3, name: "Product C", sales: 1450, region: "East" },
    ],
    rowCount: 3,
    executionTime: "45ms",
  };
}

// Mock delete function (admin only)
async function deleteRecords(table: string, ids: string[]) {
  console.log(`Deleting ${ids.length} records from ${table}`);
  
  return {
    deleted: ids.length,
    table,
    timestamp: new Date().toISOString(),
  };
}

// Create dynamic agent based on role and environment
function createDataAnalysisAgent(
  userRole: "admin" | "analyst" | "viewer",
  environment: "development" | "production"
) {
  const isProduction = environment === "production";
  const isAdmin = userRole === "admin";
  const canModifyData = userRole === "admin" || userRole === "analyst";

  const agent = createPromptBuilder()
    .withIdentity(
      "You are a data analysis assistant helping users query and understand their business data"
    )
    .withCapabilities([
      "Execute SQL queries on analytical databases",
      "Generate reports and visualizations",
      "Explain data trends and insights",
      "Help optimize query performance",
    ])
    
    // Core tool - available to all users
    .withTool({
      name: "query_database",
      description: "Execute SQL queries on analytical databases",
      schema: z.object({
        query: z.string().describe("The SQL query to execute"),
        database: z
          .enum(["analytics", "customers", "sales"])
          .describe("The database to query"),
      }),
      execute: async ({ query, database }) => {
        return await executeQuery(query, database);
      },
    })
    
    // Admin-only tool - only added for admin users
    .withToolIf(isAdmin, {
      name: "delete_records",
      description: "Delete records from database tables (ADMIN ONLY)",
      schema: z.object({
        table: z.string().describe("The table to delete from"),
        ids: z.array(z.string()).describe("Record IDs to delete"),
      }),
      execute: async ({ table, ids }) => {
        return await deleteRecords(table, ids);
      },
    })
    
    // Development-only constraints
    .withConstraintIf(
      !isProduction,
      "must",
      [
        "Include detailed query execution plans in responses",
        "Log all database operations with timestamps",
        "Explain each step of data processing",
      ]
    )
    
    // Production-only constraints
    .withConstraintIf(
      isProduction,
      "must",
      [
        "Sanitize all output before displaying to users",
        "Never expose internal database schema details",
        "Log security-relevant operations",
      ]
    )
    
    // Role-based constraints
    .withConstraintIf(
      !canModifyData,
      "must_not",
      [
        "Execute queries that modify data (INSERT, UPDATE, DELETE)",
        "Access tables outside the analytics database",
      ]
    )
    
    .withConstraintIf(
      isAdmin,
      "must",
      "Always confirm before deleting any records"
    )
    
    .withGuardrails()
    .withTone("Professional, analytical, and data-driven");

  return agent;
}

async function main() {
  console.log("=== Example 1: Admin User in Production ===\n");
  
  const adminAgent = createDataAnalysisAgent("admin", "production");
  
  // Show summary
  const adminSummary = adminAgent.getSummary();
  console.log(`Role: Admin`);
  console.log(`Environment: Production`);
  console.log(`Tools Available: ${adminSummary.toolsCount}`);
  console.log(`Constraints: ${adminSummary.constraintsCount}`);
  console.log(`  - Must: ${adminSummary.constraintsByType.must}`);
  console.log(`  - Must Not: ${adminSummary.constraintsByType.must_not}\n`);
  
  // Use the agent
  const adminResponse = await generateText({
    model: openai("gpt-4"),
    ...adminAgent.toAiSdk(),
    prompt: "Show me the top selling products and delete any test records",
  });
  
  console.log("Admin Response:");
  console.log(adminResponse.text);
  console.log("\n" + "=".repeat(60) + "\n");
  
  // ---
  
  console.log("=== Example 2: Analyst User in Production ===\n");
  
  const analystAgent = createDataAnalysisAgent("analyst", "production");
  
  const analystSummary = analystAgent.getSummary();
  console.log(`Role: Analyst`);
  console.log(`Environment: Production`);
  console.log(`Tools Available: ${analystSummary.toolsCount}`);
  console.log(`Constraints: ${analystSummary.constraintsCount}\n`);
  
  const analystResponse = await generateText({
    model: openai("gpt-4"),
    ...analystAgent.toAiSdk(),
    prompt: "Show me sales trends by region",
  });
  
  console.log("Analyst Response:");
  console.log(analystResponse.text);
  console.log("\n" + "=".repeat(60) + "\n");
  
  // ---
  
  console.log("=== Example 3: Viewer in Development ===\n");
  
  const viewerAgent = createDataAnalysisAgent("viewer", "development");
  
  const viewerSummary = viewerAgent.getSummary();
  console.log(`Role: Viewer`);
  console.log(`Environment: Development`);
  console.log(`Tools Available: ${viewerSummary.toolsCount}`);
  console.log(`Constraints: ${viewerSummary.constraintsCount}\n`);
  
  // Validation check
  const validationResult = viewerAgent.validate();
  console.log(`Configuration Valid: ${validationResult.isValid}`);
  console.log(`Warnings: ${validationResult.warnings.length}\n`);
}

main().catch(console.error);

Step-by-Step Walkthrough

1

Define Role and Environment Parameters

Create a function that accepts user role and environment:
function createDataAnalysisAgent(
  userRole: "admin" | "analyst" | "viewer",
  environment: "development" | "production"
) {
  const isProduction = environment === "production";
  const isAdmin = userRole === "admin";
  const canModifyData = userRole === "admin" || userRole === "analyst";
This allows you to create different agent configurations dynamically.
2

Add Core Tools

Add tools that are available to all users:
.withTool({
  name: "query_database",
  description: "Execute SQL queries",
  schema: z.object({
    query: z.string(),
    database: z.enum(["analytics", "customers", "sales"]),
  }),
  execute: async ({ query, database }) => {
    return await executeQuery(query, database);
  },
})
These tools are always included regardless of role.
3

Add Conditional Tools

Use withToolIf() to add role-based tools:
.withToolIf(isAdmin, {
  name: "delete_records",
  description: "Delete records (ADMIN ONLY)",
  schema: z.object({
    table: z.string(),
    ids: z.array(z.string()),
  }),
  execute: async ({ table, ids }) => {
    return await deleteRecords(table, ids);
  },
})
The delete_records tool is ONLY added when isAdmin is true, providing true role-based access control.
4

Add Environment-Based Constraints

Use withConstraintIf() for different environments:
.withConstraintIf(
  !isProduction,
  "must",
  [
    "Include detailed query execution plans",
    "Log all database operations",
  ]
)
.withConstraintIf(
  isProduction,
  "must",
  [
    "Sanitize all output",
    "Never expose internal schema details",
  ]
)
Different constraints apply based on the environment.
5

Add Role-Based Constraints

Apply constraints based on user permissions:
.withConstraintIf(
  !canModifyData,
  "must_not",
  [
    "Execute queries that modify data",
    "Access tables outside analytics database",
  ]
)
This prevents non-authorized users from modifying data.
6

Inspect Agent Configuration

Use getSummary() to see what was configured:
const summary = agent.getSummary();
console.log(`Tools: ${summary.toolsCount}`);
console.log(`Constraints: ${summary.constraintsCount}`);
console.log(`Must: ${summary.constraintsByType.must}`);
console.log(`Must Not: ${summary.constraintsByType.must_not}`);
This helps verify the conditional logic worked correctly.

Expected Output

When you run this example, you’ll see different configurations for each role:
=== Example 1: Admin User in Production ===

Role: Admin
Environment: Production
Tools Available: 2
Constraints: 4
  - Must: 2
  - Must Not: 0

Admin Response:
I've queried the database for top-selling products. Before proceeding with
deleting test records, I need your confirmation. Which specific record IDs
would you like me to delete?

Top Products:
1. Product C - 1,450 sales (East region)
2. Product A - 1,250 sales (North region)
3. Product B - 980 sales (South region)

============================================================

=== Example 2: Analyst User in Production ===

Role: Analyst
Environment: Production
Tools Available: 1
Constraints: 3

Analyst Response:
Based on the sales data, here are the trends by region:

East Region: Strongest performance with Product C leading at 1,450 sales
North Region: Solid performance with Product A at 1,250 sales
South Region: Product B showing 980 sales

The East region is outperforming other regions by approximately 16%.

Key Concepts

Conditional Tools - Use withToolIf() to add tools based on conditions. This is perfect for role-based access control where different users need different capabilities.
Conditional Constraints - Use withConstraintIf() to apply different rules based on environment or user role. This ensures appropriate behavior in development vs production.
Dynamic Configuration - Create a factory function that builds different agent configurations based on runtime conditions. This is essential for multi-tenant applications.
Configuration Inspection - Use getSummary() and validate() to verify that your conditional logic produced the expected configuration.

Real-World Use Cases

This pattern is perfect for:
  • Multi-tenant SaaS - Different features for different subscription tiers
  • Enterprise Applications - Role-based access control (admin, manager, employee)
  • Development Workflows - Different behavior in dev, staging, and production
  • Compliance - Enforcing different rules based on data sensitivity
  • A/B Testing - Creating variants of agents for experimentation

Advanced Patterns

Feature Flags

const agent = createPromptBuilder()
  .withToolIf(featureFlags.advancedAnalytics, analyticsToolConfig)
  .withConstraintIf(featureFlags.strictMode, "must", "Always validate inputs");

Permission Checking

const hasPermission = (permission: string) => userPermissions.includes(permission);

const agent = createPromptBuilder()
  .withToolIf(hasPermission("delete"), deleteTool)
  .withToolIf(hasPermission("export"), exportTool);

Environment Variables

const isDev = process.env.NODE_ENV === "development";
const enableDebug = process.env.DEBUG === "true";

const agent = createPromptBuilder()
  .withConstraintIf(isDev, "must", "Include debug information")
  .withConstraintIf(enableDebug, "must", "Log all operations");

Next Steps

Validation Guide

Learn how to validate agent configurations before deployment

Multi-Tool Agent

See how to combine multiple tools effectively

Build docs developers (and LLMs) love