> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runflow.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Feedback Analysis System

> Build an automated pipeline that analyzes sentiment, categorizes feedback, and creates tickets

An automated feedback processing pipeline that analyzes sentiment with AI, categorizes issues, and creates tickets for negative feedback. This example shows how to combine workflows with agents and connectors for a hands-off analysis system.

## Project Structure

```
feedback-analysis/
├── main.ts
├── workflows/
│   └── analyze-feedback.ts
├── agents/
│   ├── sentiment.ts
│   └── action-recommender.ts
├── config/
│   └── settings.ts
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json
```

## Step 1: Configuration

```typescript config/settings.ts theme={null}
export const FEEDBACK_CATEGORIES = [
  'product_bug',
  'feature_request',
  'ux_issue',
  'performance',
  'documentation',
  'pricing',
  'support_quality',
  'praise',
  'other',
] as const;

export type FeedbackCategory = (typeof FEEDBACK_CATEGORIES)[number];

export const SENTIMENT_THRESHOLDS = {
  negative: -0.3, // Below this = negative
  positive: 0.3,  // Above this = positive
};
```

## Step 2: Analysis Agents

### Sentiment Analyzer

Cheap, fast model for classification:

```typescript agents/sentiment.ts theme={null}
import { Agent, openai } from '@runflow-ai/sdk';

export const sentimentAgent = new Agent({
  name: 'Sentiment Analyzer',
  instructions: `You analyze customer feedback for sentiment and categorization.

## Task
Analyze the feedback and return a structured analysis.

## Response Format
Respond with valid JSON only:
{
  "sentiment": "positive" | "neutral" | "negative",
  "score": <number between -1.0 and 1.0>,
  "category": "<one of: product_bug, feature_request, ux_issue, performance, documentation, pricing, support_quality, praise, other>",
  "themes": ["<key theme 1>", "<key theme 2>"],
  "summary": "<one sentence summary of the feedback>"
}`,
  model: openai('gpt-4o-mini'),
  modelConfig: { temperature: 0 },
});
```

### Action Recommender

Better model for nuanced recommendations:

```typescript agents/action-recommender.ts theme={null}
import { Agent, openai } from '@runflow-ai/sdk';

export const actionAgent = new Agent({
  name: 'Action Recommender',
  instructions: `You recommend specific actions based on customer feedback analysis.

## Task
Given the original feedback and sentiment analysis, recommend concrete actions.

## Response Format
Respond with valid JSON:
{
  "priority": "critical" | "high" | "medium" | "low",
  "actions": [
    { "action": "<what to do>", "owner": "<team: product | engineering | support | marketing>" }
  ],
  "shouldFollowUp": <boolean>,
  "followUpMessage": "<suggested response to the customer, if applicable>"
}`,
  model: openai('gpt-4o'),
  modelConfig: { temperature: 0.3 },
});
```

## Step 3: Workflow

The workflow chains analysis steps and conditionally creates tickets:

```typescript workflows/analyze-feedback.ts theme={null}
import { createWorkflow } from '@runflow-ai/sdk';
import { z } from 'zod';
import { sentimentAgent } from '../agents/sentiment';
import { actionAgent } from '../agents/action-recommender';

export const analyzeFeedbackWorkflow = createWorkflow({
  id: 'analyze-feedback',
  inputSchema: z.object({
    feedback: z.string(),
    customerEmail: z.string(),
    customerName: z.string(),
    source: z.string(),
  }),
  outputSchema: z.any(),
})
  // Step 1: Analyze sentiment
  .agent('analyze', sentimentAgent, {
    promptTemplate: `Analyze this customer feedback:

"{{input.feedback}}"

Customer: {{input.customerName}} ({{input.customerEmail}})
Source: {{input.source}}`,
  })

  // Step 2: Recommend actions
  .agent('recommend', actionAgent, {
    promptTemplate: `Original feedback: "{{input.feedback}}"
Customer: {{input.customerName}}

Sentiment analysis:
{{analyze.text}}

What actions should we take?`,
  })

  // Step 3: Create ticket if negative
  .condition(
    'check-negative',
    (ctx) => {
      try {
        const analysis = JSON.parse(ctx.stepResults.get('analyze').text);
        return analysis.sentiment === 'negative';
      } catch {
        return false;
      }
    },
    // Negative path: create ticket
    [
      {
        id: 'create-ticket',
        type: 'connector',
        config: {
          connector: 'hubspot',
          resource: 'tickets',
          action: 'create',
          parameters: {
            subject: 'Negative Feedback — {{input.customerName}}',
            content: `Feedback: {{input.feedback}}

Analysis: {{analyze.text}}

Recommended actions: {{recommend.text}}`,
            priority: 'high',
            category: 'feedback',
          },
        },
      },
    ],
    // Positive/neutral path: log only
    [
      {
        id: 'log-feedback',
        type: 'function',
        config: {
          execute: async (input, ctx) => {
            return { logged: true, sentiment: 'positive_or_neutral' };
          },
        },
      },
    ]
  )
  .build();
```

## Step 4: Main Entry Point

```typescript main.ts theme={null}
import { identify, track } from '@runflow-ai/sdk/observability';
import { analyzeFeedbackWorkflow } from './workflows/analyze-feedback';

export async function main(input: any) {
  // Validate
  if (!input?.feedback) {
    return { error: 'feedback is required' };
  }

  identify(input.customerEmail || input.customerId || 'anonymous');

  try {
    const result = await analyzeFeedbackWorkflow.execute({
      feedback: input.feedback,
      customerEmail: input.customerEmail || '',
      customerName: input.customerName || 'Unknown',
      source: input.source || 'api',
    });

    // Parse results for metrics
    let sentiment = 'unknown';
    let category = 'unknown';
    try {
      const analysis = JSON.parse(result.stepResults?.analyze?.text || '{}');
      sentiment = analysis.sentiment;
      category = analysis.category;
    } catch {}

    // Track feedback metrics
    track('feedback_analyzed', {
      sentiment,
      category,
      source: input.source || 'api',
      ticketCreated: sentiment === 'negative',
    });

    return {
      message: `Feedback analyzed: ${sentiment} sentiment, category: ${category}`,
      analysis: result.stepResults?.analyze?.text,
      actions: result.stepResults?.recommend?.text,
      ticketCreated: sentiment === 'negative',
    };
  } catch (error) {
    console.error('[feedback-analysis] Error:', error);
    return { error: 'Failed to analyze feedback' };
  }
}
```

## Triggering the Pipeline

This workflow is typically triggered by external events, not user conversations:

```bash theme={null}
# From a webhook (NPS survey, support form, review platform)
curl -X POST https://your-agent.runflow.ai/api \
  -H "Content-Type: application/json" \
  -d '{
    "feedback": "The product crashes every time I try to export. Very frustrated.",
    "customerEmail": "jane@example.com",
    "customerName": "Jane Doe",
    "source": "nps_survey"
  }'
```

## How It Works

```
Feedback arrives (webhook, form, API)
       ↓
┌────────────────────┐
│  Sentiment Agent    │  gpt-4o-mini → sentiment, category, themes
└──────┬─────────────┘
       ↓
┌────────────────────┐
│  Action Agent       │  gpt-4o → priority, actions, follow-up
└──────┬─────────────┘
       ↓
   Negative?
  ╱         ╲
 Yes         No
  ↓           ↓
Create     Log for
HubSpot    analytics
ticket
  ↓
Track metrics
```

## Key Patterns

### Pipeline vs Conversation

This is a **workflow** (pipeline), not an agent (conversation). Data flows in, gets processed, and comes out — no back-and-forth with a user. Workflows are ideal for batch processing and event-driven automations.

### Cheap Classification, Quality Recommendations

The sentiment agent uses `gpt-4o-mini` (fast, cheap). The action recommender uses `gpt-4o` (better reasoning). Match model cost to task complexity.

### Structured JSON for Reliable Branching

Both agents return structured JSON so the workflow can reliably branch on results:

```typescript theme={null}
// Agent returns: { "sentiment": "negative", ... }
// Workflow condition parses and branches:
.condition('check', (ctx) => {
  const analysis = JSON.parse(ctx.stepResults.get('analyze').text);
  return analysis.sentiment === 'negative';
})
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Workflows" icon="diagram-project" href="/core-concepts/workflows">
    Learn more about workflows
  </Card>

  <Card title="Connectors" icon="plug" href="/core-concepts/connectors">
    HubSpot, Slack integrations
  </Card>

  <Card title="Multi-Agent System" icon="users" href="/use-cases/multi-agent-system">
    Supervisor pattern
  </Card>

  <Card title="Observability" icon="chart-line" href="/core-concepts/observability">
    Track business metrics
  </Card>
</CardGroup>
