# Enrichments Reference

Complete specification of all RicherText enrichment types.

---

## Common Parameters

All enrichments share these parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Unique identifier for this enrichment. Used in output column names. |
| `type` | string | Yes | One of: `classifier`, `labeler`, `summarizer`, `scorer`, `reasoner` |
| `input_columns` | list[string] | Yes | CSV columns to inject into the prompt via `{column_name}` placeholders. |
| `prompt` | string | No | Inline prompt template. Use `{column_name}` for variable substitution. |
| `prompt_key` | string | No | Reference to a prompt in the external prompts file. |

**Note:** Either `prompt` or `prompt_key` must be provided. If both are present, `prompt_key` takes precedence.

---

## Classifier

Classify each record into exactly ONE category.

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `categories` | list[string] | Required | Possible categories to choose from. |
| `output_column` | string | `{name}_category` | Override the output column name. |
| `include_reasoning` | boolean | `false` | Include explanation for the classification. |
| `reasoning_max_length` | integer | `200` | Maximum characters for reasoning (when enabled). |

### Output Columns

| Column | Condition | Description |
|--------|-----------|-------------|
| `{name}_category` | Always | The selected category. |
| `{name}_category_reasoning` | `include_reasoning: true` | Explanation of why this category was chosen. |

### Example

```yaml
- name: sentiment
  type: classifier
  categories:
    - positive
    - negative
    - neutral
  input_columns: [review_text]
  include_reasoning: true
  prompt: |
    Classify the sentiment of this review:

    {review_text}
```

### Behavior

- The LLM must select exactly one category from the provided list.
- If the response doesn't match any category, RicherText attempts fuzzy matching.
- If no match is found, the value is set to `"Unknown"`.

---

## Labeler

Apply MULTIPLE labels to each record (multi-label classification).

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `labels` | list[string] | Required | Possible labels to apply. |
| `output_column` | string | `{name}_labels` | Override the output column name. |
| `include_reasoning` | boolean | `false` | Include explanation for each label. |
| `reasoning_max_length` | integer | `200` | Maximum characters for reasoning (when enabled). |

### Output Columns

| Column | Condition | Description |
|--------|-----------|-------------|
| `{name}_labels` | Always | Semicolon-separated list of applied labels, alphabetically sorted. |
| `{name}_labels_reasoning` | `include_reasoning: true` | Explanation of why each label was applied. |

### Example

```yaml
- name: topics
  type: labeler
  labels:
    - pricing
    - quality
    - support
    - shipping
    - returns
  input_columns: [feedback]
  prompt: |
    What topics does this customer feedback discuss?

    {feedback}
```

### Behavior

- The LLM can select zero, one, or multiple labels.
- Only labels from the provided list are accepted; others are filtered out.
- Labels are sorted alphabetically in the output.
- Multiple labels are joined with `; ` (semicolon + space).

---

## Summarizer

Generate a text summary of the input.

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `max_length` | integer | `200` | Maximum summary length in characters. |
| `output_column` | string | `{name}_summary` | Override the output column name. |

### Output Columns

| Column | Condition | Description |
|--------|-----------|-------------|
| `{name}_summary` | Always | The generated summary text. |

### Example

```yaml
- name: brief
  type: summarizer
  max_length: 100
  input_columns: [title, description]
  prompt: |
    Summarize this job posting in one sentence:

    Title: {title}
    Description: {description}
```

### Behavior

- The LLM is instructed to keep the summary within `max_length` characters.
- Actual length may vary slightly as this is a soft limit.
- No preamble like "This is a summary of..." is added.

---

## Scorer

Rate a record on a single criterion using a numeric scale.

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `output_column` | string | `{name}_score` | Override the output column name. |
| `scale_min` | integer | `1` | Minimum score value. |
| `scale_max` | integer | `10` | Maximum score value. |
| `include_reasoning` | boolean | `false` | Include explanation for the score. |
| `reasoning_max_length` | integer | `200` | Maximum characters for reasoning (when enabled). |

### Output Columns

| Column | Condition | Description |
|--------|-----------|-------------|
| `{name}_score` | Always | Numeric score. |
| `{name}_score_reasoning` | `include_reasoning: true` | Explanation for the score. |

### Example

```yaml
- name: clarity
  type: scorer
  scale_min: 1
  scale_max: 10
  input_columns: [response]
  include_reasoning: true
  prompt: |
    Rate the clarity of this response (how easy is it to understand?):

    {response}

- name: completeness
  type: scorer
  scale_min: 1
  scale_max: 10
  input_columns: [response]
  prompt: |
    Rate how complete this response is (does it fully address the question?):

    {response}
```

**Output columns:** `clarity_score`, `clarity_score_reasoning`, `completeness_score`

### Behavior

- Each scorer enrichment produces one score based on the prompt.
- For multiple scoring criteria, use multiple scorer enrichments.
- Scores are returned as integers within the defined range.
- If parsing fails, the score is left empty.

---

## Reasoner

Generate free-form analysis or reasoning about the input.

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `output_column` | string | `{name}_analysis` | Override the output column name. |

### Output Columns

| Column | Condition | Description |
|--------|-----------|-------------|
| `{name}_analysis` | Always | Free-form analysis text. |

### Example

```yaml
- name: assessment
  type: reasoner
  input_columns: [data, context]
  prompt: |
    Analyze this data and provide insights:

    Data: {data}
    Context: {context}
```

### Behavior

- No structured output format is enforced.
- The LLM provides open-ended analysis.
- Useful for synthesizing information from multiple sources.
- Often used as the final step in a chain after classifiers/labelers.

---

## Chaining Enrichments

Enrichments run sequentially. Later enrichments can reference output from earlier ones.

### How It Works

1. Each enrichment adds its output columns to the record.
2. Subsequent enrichments can include those columns in `input_columns`.
3. Use the full column name (e.g., `sentiment_category`, not just `sentiment`).

### Example: Classification → Reasoning → Scoring

```yaml
enrichments:
  # Stage 1: Classify with reasoning
  - name: sentiment
    type: classifier
    categories: [positive, negative, neutral]
    input_columns: [text]
    include_reasoning: true

  # Stage 2: Synthesize using classification results
  - name: analysis
    type: reasoner
    input_columns:
      - text
      - sentiment_category
      - sentiment_category_reasoning
    prompt: |
      Based on the sentiment analysis, provide a deeper assessment.

      Text: {text}
      Sentiment: {sentiment_category}
      Reasoning: {sentiment_category_reasoning}

  # Stage 3: Score using all previous outputs
  - name: urgency
    type: scorer
    input_columns:
      - text
      - sentiment_category
      - analysis_analysis
    prompt: |
      How urgently should we address this feedback?

      Text: {text}
      Sentiment: {sentiment_category}
      Analysis: {analysis_analysis}

  - name: importance
    type: scorer
    input_columns:
      - text
      - analysis_analysis
    prompt: |
      How important is this feedback to address?

      Text: {text}
      Analysis: {analysis_analysis}
```

### Output Column Reference

| Enrichment Type | Output Columns |
|-----------------|----------------|
| Classifier | `{name}_category`, `{name}_category_reasoning` |
| Labeler | `{name}_labels`, `{name}_labels_reasoning` |
| Summarizer | `{name}_summary` |
| Scorer | `{name}_score`, `{name}_score_reasoning` |
| Reasoner | `{name}_analysis` |

---

## Prompts

### Inline vs External

**Inline prompt** (in taskflow):
```yaml
- name: sentiment
  type: classifier
  categories: [positive, negative]
  input_columns: [text]
  prompt: |
    Classify the sentiment:
    {text}
```

**External prompt** (in prompts file):
```yaml
# taskflow.yaml
prompts_file: ../prompts/prompts.yaml

enrichments:
  - name: sentiment
    type: classifier
    categories: [positive, negative]
    input_columns: [text]
    prompt_key: sentiment_prompt
```

```yaml
# prompts.yaml
sentiment_prompt: |
  Classify the sentiment:
  {text}
```

### Variable Substitution

Use `{column_name}` to inject values from:
- Original CSV columns
- Output columns from previous enrichments

```yaml
prompt: |
  The {customer_tier} customer said: {feedback}
  Previous sentiment: {sentiment_category}
```

---

## Error Handling

When an enrichment fails:

1. **Parsing errors**: Output columns are set to empty strings.
2. **API errors**: Retried with exponential backoff (up to 5 attempts).
3. **Rate limits**: Automatically throttled by the token bucket rate limiter.

Failed records are still written to output with empty enrichment columns, allowing the pipeline to continue.

---

## Performance Notes

- Enrichments within a record run sequentially (required for chaining).
- Records are processed in parallel across workers.
- Worker count is auto-calculated from model rate limits.
- Each enrichment = 1 API call per record.
