Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/rules/coding-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Coding Style

- Use `with` as much as possible for flow control
- Use pipe operator (|>) for chaining
- Prefer simple functions with patterns matching over complex ones
- Limit line length to 80 characters
- Prefer case statements for multi-branch conditionals
- Always create typespecs for public functions but avoid `any()` and `term()` types when possible
- Use descriptive function names with ? suffix for boolean functions

# Elixir Style Guide
Follow: https://github.com/christopheradams/elixir_style_guide

# Docs
- Put all `.md` files except `README` and `AGENTS` in `docs/` folder
- Use `@doc` and `@moduledoc` for inline documentation

15 changes: 15 additions & 0 deletions .claude/rules/workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Feature Implementation Flow

! Never chsnge branches or commit anything to `git` without confirmation!

1. Plan the feature and ask for feedback
2. Implement tests first (TDD) and ask for approve
3. Implement the feature code
4. Make sure code is compilable by `mix compile` and has no warnings
5. Run all tests with `mix test` and ensure they pass
6. Make sure code runs in `iex -S mix` without errors
7. Check with `mix dialyzer` for type issues
8. Update documentation and AGENTS.md if needed
9. Run `mix format` to ensure code style compliance
10. Open `nvim` so I could review the code and give feedback

75 changes: 75 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,80 @@
This is a web application written using the Phoenix web framework.

## Project Structure

### Key Features

#### Daily Dialog Summarization System

The application includes an automatic dialog summarization system to optimize AI context and reduce API costs:

**Database Schema:**
- **Migration:** `priv/repo/migrations/20260127085112_create_message_summaries.exs`
- **`message_summaries`** table stores daily summaries with fields:
- `chat_id` (references chats, on_delete: :delete_all) - Which chat
- `summary_text` (text, not null) - AI-generated summary
- `summary_date` (date, not null) - Date being summarized
- `message_count` (integer, default: 0) - Number of messages
- `start_time`, `end_time` (naive_datetime) - Temporal boundaries
- `ai_model` (string) - Which AI backend generated the summary
- `inserted_at`, `updated_at` (timestamps)
- **Indexes:**
- Unique index on `(chat_id, summary_date)` for idempotency
- Index on `chat_id` for fast lookups
- Index on `summary_date` for date-based queries

**Core Modules:**
- **`Bodhi.Chats.Summary`** (`lib/bodhi/chats/summary.ex`) - Ecto schema for message_summaries table
- **`Bodhi.Chats`** (`lib/bodhi/chats.ex`) - Extended with summary functions:
- `get_chat_context_for_ai/2` - Main context assembly function (line ~168)
- `get_summaries_before_date/2`, `get_recent_messages/2` - Query helpers
- `get_active_chats_for_date/1`, `get_messages_for_date/2` - Date-based queries
- `create_summary/1`, `get_summary/2` - CRUD operations
- `build_context/3` (private) - Context builder
- **`Bodhi.Chats.Summarizer`** (`lib/bodhi/chats/summarizer.ex`) - Shared summarization logic
- `generate_and_store/3` - Calls AI and persists summary
- `build_summarization_prompt/1` - Builds prompt for AI
- `current_ai_model/0` - Returns configured AI model name
- **`Bodhi.Workers.DailyChatSummarizer`** (`lib/bodhi/workers/daily_chat_summarizer.ex`) - Oban worker
- Runs daily at 2 AM UTC via Oban Cron plugin
- Processes all active chats sequentially
- Creates summaries for previous day's messages
- Refactored to reduce nesting depth (credo compliant)
- **`Bodhi.Release`** (`lib/bodhi/release.ex`) - Release tasks including:
- `backfill_summaries/1` - Migration tool for historical data (line ~26)
- Supports dry-run, date ranges, and chat filtering
- **`Bodhi.TgWebhookHandler`** (`lib/bodhi/tg_webhook_handler.ex`) - Updated at line ~130
- Uses `get_chat_context_for_ai/2` instead of `get_chat_messages/1`

**Context Assembly:**
- `Bodhi.Chats.get_chat_context_for_ai/2` assembles context from:
- Summaries for messages older than 7 days (configurable)
- Full messages from last 7 days
- Used by `TgWebhookHandler.get_answer/2` instead of `get_chat_messages/1`
- Gracefully falls back to recent messages when no summaries exist

**Configuration:**
```elixir
# config/config.exs
config :bodhi, :summarization, recent_days: 7

config :bodhi, Oban,
plugins: [
{Oban.Plugins.Cron,
crontab: [{"0 2 * * *", Bodhi.Workers.DailyChatSummarizer}]}
]
```

**Documentation:**
- [docs/SUMMARIZATION.md](docs/SUMMARIZATION.md) - Implementation details and usage
- [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) - Deployment and backfill procedures

**Key Guidelines:**
- Summaries are idempotent - safe to regenerate
- Worker processes chats sequentially to respect rate limits
- Backfill tool supports dry-run mode for cost estimation
- Summary messages use `user_id: -1` as a special marker

## Project guidelines

- Use `mix precommit` alias when you are done with all changes and fix any pending issues
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,40 @@ Edit `lib/bodhi/open_router.ex` and modify the `@default_model` attribute:

See all available models at: https://openrouter.ai/models

## Features

### Daily Dialog Summarization

Bodhi automatically summarizes chat conversations daily to optimize AI context and reduce API costs:

- **Automatic Summarization**: Worker runs daily at 2 AM UTC to summarize previous day's messages
- **Smart Context Assembly**: Uses summaries for older messages + full messages from last 7 days
- **Cost Optimization**: Reduces token usage by ~80-90% for long conversations
- **Seamless Integration**: Falls back gracefully when no summaries exist

**Example Results:**
- 265 total messages → 4 recent messages in context
- **98.5% token reduction** for older conversations

#### Documentation

- **[docs/SUMMARIZATION.md](docs/SUMMARIZATION.md)** - Complete guide to the summarization system
- **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** - Deployment and backfill procedures

#### Quick Start

After deployment, backfill historical summaries:

```bash
# Preview what will be processed (no API calls)
bin/bodhi eval "Bodhi.Release.backfill_summaries(dry_run: true)"

# Run the backfill
bin/bodhi eval "Bodhi.Release.backfill_summaries()"
```

See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for detailed instructions.

## Learn more

* Official website: https://www.phoenixframework.org/
Expand Down
10 changes: 10 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,18 @@ config :telegex,
config :bodhi, Oban,
engine: Oban.Engines.Basic,
queues: [default: 10, messages: 10],
plugins: [
{Oban.Plugins.Cron,
crontab: [
# Run daily at 2 AM UTC
{"0 2 * * *", Bodhi.Workers.DailyChatSummarizer}
]}
],
repo: Bodhi.Repo

# Summarization settings
config :bodhi, :summarization, recent_days: 7

config :posthog,
enable: true,
api_host: "https://eu.i.posthog.com"
Expand Down
127 changes: 127 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Deployment Guide

## Database Migrations

After deploying a new version, run database migrations:

```bash
bin/bodhi eval "Bodhi.Release.migrate()"
```

## Post-Deployment Tasks

### Backfill Message Summaries (After Summarization Feature Deployment)

After deploying the summarization feature for the first time, you should backfill summaries for historical messages.

#### Step 1: Preview (Dry Run)

First, run a dry run to see what would be processed without making any AI calls:

```bash
bin/bodhi eval "Bodhi.Release.backfill_summaries(dry_run: true)"
```

This will show:
- How many chats will be processed
- How many days have messages for each chat
- How many summaries would be created
- How many days would be skipped (no messages or already have summaries)

#### Step 2: Estimate Costs

Calculate AI API costs based on the dry run output. Each summary requires one AI API call.

Example calculation:
- Chat 1: 100 days with messages = 100 API calls
- Chat 2: 50 days with messages = 50 API calls
- Total: 150 API calls × $0.XX per call = $XX.XX

#### Step 3: Run Backfill

Once you've confirmed the scope and cost, run the actual backfill:

```bash
# Full backfill (all chats, all dates)
bin/bodhi eval "Bodhi.Release.backfill_summaries()"

# Or with specific parameters
bin/bodhi eval "Bodhi.Release.backfill_summaries(from_date: ~D[2024-01-01], to_date: ~D[2024-12-31])"

# Or for specific chats only
bin/bodhi eval "Bodhi.Release.backfill_summaries(chat_ids: [123, 456])"
```

#### Step 4: Monitor Progress

Monitor the logs for progress and any errors:

```bash
tail -f /path/to/logs/production.log | grep -E "backfill|summary"
```

You should see logs like:
```
[info] Starting summary backfill (dry_run: false)
[info] Found 5 chats to process
[info] Processing chat 123 from 2024-01-01 to 2024-12-31
[debug] Creating summary for chat 123 on 2024-01-01 (15 messages)
[info] Chat 123: 365 summaries created, 0 days skipped
[info] Backfill complete: 5 chats processed, 1825 summaries created
```

#### Step 5: Verify Results

Check that summaries were created:

```bash
bin/bodhi eval "Bodhi.Repo.aggregate(Bodhi.Chats.Summary, :count)"
```

## Rollback Procedure

If you need to rollback a deployment:

```bash
# Rollback to a specific migration version
bin/bodhi eval "Bodhi.Release.rollback(Bodhi.Repo, VERSION_NUMBER)"
```

## Troubleshooting

### Backfill Fails with AI API Errors

If the backfill fails due to AI API rate limiting or errors:

1. The backfill is idempotent - you can safely re-run it
2. It will skip dates that already have summaries
3. Consider using date ranges to backfill in smaller batches:

```bash
# Backfill one month at a time
bin/bodhi eval "Bodhi.Release.backfill_summaries(from_date: ~D[2024-01-01], to_date: ~D[2024-01-31])"
bin/bodhi eval "Bodhi.Release.backfill_summaries(from_date: ~D[2024-02-01], to_date: ~D[2024-02-29])"
# etc.
```

### Out of Memory

If the backfill runs out of memory with many chats:

1. Process chats in batches using the `chat_ids` parameter:

```bash
# Get list of chat IDs first
bin/bodhi eval "Bodhi.Repo.all(Ecto.Query.from m in Bodhi.Chats.Message, distinct: m.chat_id, select: m.chat_id)"

# Then backfill in batches
bin/bodhi eval "Bodhi.Release.backfill_summaries(chat_ids: [1, 2, 3])"
bin/bodhi eval "Bodhi.Release.backfill_summaries(chat_ids: [4, 5, 6])"
# etc.
```

## Scheduled Tasks

The summarization worker runs automatically daily at 2 AM UTC via Oban cron. No manual intervention needed for daily summaries after the initial backfill.

Monitor scheduled tasks via the Oban dashboard at `/oban` in your web interface (requires admin access).
Loading
Loading