Item Generation Service
April 29, 2026 · View on GitHub
Intelligent, context-aware item generation for Listopia's pre-creation planning system.
Overview
ItemGenerationService generates appropriate list items based on user context. It is fully domain-agnostic and works with ANY type of list (events, courses, reading lists, recipes, projects, etc.). Instead of hardcoded logic for specific use cases, it uses a single generic approach powered by Claude's reasoning capabilities.
Key principle: The LLM analyzes the planning context and subdivision type to intelligently determine what items are needed. There are NO hardcoded rules for specific domains (events, projects, travel, learning, etc.) - the system adapts to whatever the user is planning.
When It's Used
ItemGenerationService is called during the pre-creation planning phase when a user's request is detected as "complex" and requires clarification. It works equally for any domain:
Example 1: Event Planning (Roadshow)
User: "Help me plan our roadshow for Listopia"
↓ (detected as complex)
User: Provides locations, budget, dates
↓
ItemGenerationService: Called for each location
↓
Sublists created with roadshow-specific items per location
Example 2: Course Planning (Reading List)
User: "Create a reading list about machine learning"
↓ (detected as complex)
User: Provides books, topics, time available
↓
ItemGenerationService: Called for each book
↓
Sublists created with reading-specific items per book
Example 3: Project Planning
User: "Plan a product launch for next quarter"
↓ (detected as complex)
User: Provides phases, team members, budget
↓
ItemGenerationService: Called for each phase
↓
Sublists created with phase-appropriate tasks
Architecture
Service Location
app/services/item_generation_service.rb
Key Dependencies
- RubyLLM 1.14+ - LLM integration
- gpt-5.4-2026-03-05 - Reasoning model for sophisticated item generation
- ApplicationService - Base service class
Call Flow
# In ChatCompletionService#handle_pre_creation_planning_response or ChatContextToListService
result = ItemGenerationService.new(
list_title: "roadshow for Listopia",
description: "Budget: \$500k | Timeline: June-Sept",
category: "professional",
planning_context: chat_context.parameters, # Extracted from user answers
sublist_title: "New York" # For each subdivision
).call
# Result is a service Result object
if result.success?
items = result.data # Array of item hashes with title, description, type, priority
# Items stored in chat_context.generated_items for each subdivision
end
Usage
Basic Usage (for a sublist)
ItemGenerationService.new(
list_title: "Plan US Roadshow",
description: "Budget: \$500,000 | Start: June 2026",
category: "professional",
planning_context: {
locations: ["New York", "Los Angeles", "Chicago"],
budget: "\$500,000",
timeline: "June - September 2026"
},
sublist_title: "New York"
).call
Return Value
Success returns an array of item hashes:
[
{
title: "Confirm venue booking at Times Square location",
description: "Contact venue to confirm date availability and negotiate terms. Ensure capacity meets expected attendance of 200+ people.",
type: "task",
priority: "high"
},
{
title: "Arrange local transportation logistics",
description: "Book buses for airport pickups and venue transfers. Confirm with local dispatcher for day-of coordination.",
type: "task",
priority: "high"
},
...
]
Error Handling
Gracefully returns empty array on any error:
result = ItemGenerationService.new(...).call
if result.success?
items = result.data # Array, might be empty on error
else
# Service handles errors internally, returns success with []
items = []
end
Design Philosophy
Why Generic Service?
The old approach had three hardcoded methods:
generate_location_specific_items- only for locationsgenerate_phase_specific_items- only for phasesgenerate_context_aware_items- generic fallback, still limited
Problems:
- Not scalable - needed a new method for each subdivision type
- Bug-prone - each method had similar logic with subtle differences
- Hard to maintain - three places to update when fixing issues
New Approach
Single intelligent service that works for:
- ✅ Locations (roadshow planning)
- ✅ Phases (project planning, phased rollouts)
- ✅ Weeks/Chapters (learning paths, course planning)
- ✅ Sprints (agile planning)
- ✅ Sections (any custom subdivision)
- ✅ Unknown future subdivision types
The LLM automatically determines what items are appropriate based on context.
Prompt Strategy
The service uses a sophisticated prompt that:
- Identifies the domain - Event, travel, learning, project, business, etc.
- Analyzes the context - Locations, budget, timeline, category
- Generates specific items - Not generic duplicates, but items unique to each subdivision
- Considers constraints - Budget, timeline, team size, preferences
Prompt Structure
Analyze the planning request and generate 5-8 specific, actionable items.
Planning Context:
- List: "roadshow for Listopia"
- Category: professional
- Focus: Generate items SPECIFICALLY for: New York
- Budget: \$500,000
- Timeline: June 2026 - September 2026
- Locations: [list of all cities]
Your Task:
1. Understand the DOMAIN (event, travel, learning, project, business, etc.)
2. Generate items that are SPECIFIC and APPROPRIATE to New York
- NOT generic duplicates of other locations
- Consider local logistics, vendors, regulations, partnerships
3. Avoid generic placeholders
4. Return JSON array with title, description, type, priority
Model Selection
Why gpt-5.4-2026-03-05?
- Extended Thinking: Sophisticated reasoning about what items are needed
- Context Understanding: Better analysis of planning domain and requirements
- Quality: More specific, actionable items vs generic suggestions
- Reliability: Fewer parsing errors and invalid responses
Trade-offs
- Slightly slower than gpt-5-nano (~2-3s vs ~1s)
- Cost is negligible per call
- Result quality is significantly higher
- Worth the latency for user-facing feature
Implementation Details
Item Format
Items must have these fields:
{
title: String, # 1-10 words, action-oriented
description: String, # 1-3 sentences, specific details
type: String, # "task", "milestone", "reminder", "note"
priority: String # "high", "medium", "low"
}
Formatting
The service converts various input formats to consistent hash format:
# Accepts strings...
"Complete venue reservation"
# ↓ converts to ↓
{ title: "Complete venue reservation", description: "", type: "task", priority: "medium" }
# Accepts hashes...
{ "title" => "...", "description" => "..." }
# ↓ converts to ↓
{ title: "...", description: "...", type: "task", priority: "medium" }
Error Handling
The service catches and handles:
- LLM API failures → Returns empty array
- JSON parsing errors → Returns empty array
- Missing fields → Applies defaults (type: "task", priority: "medium")
- Nil inputs → Converted to empty string/array
Integration Points
Called From
ChatCompletionService or ChatContextToListService when creating nested lists with subdivisions:
:locations- multi-city events, roadshows:phases- phased projects, rollouts:books- reading lists, learning paths:modules,:sprints,:chapters- any subdivision type
Specifically called when:
- User answers pre-creation planning questions
- ChatContext contains hierarchical_items with subdivision_type
- For each subdivision, ItemGenerationService generates items
Feeds Into
ListCreationService#create_list_with_structure or direct List/ListItem creation, which uses generated items to populate sublists.
Testing
Unit Testing Example
describe ItemGenerationService do
let(:service) do
described_class.new(
list_title: "Plan US Roadshow",
description: "Budget: \$500k",
category: "professional",
planning_context: { locations: ["NYC"], budget: "\$500k" },
sublist_title: "New York"
)
end
it "returns success with items" do
result = service.call
expect(result.success?).to be true
expect(result.data).to be_an Array
expect(result.data.first.keys).to include(:title, :description, :type, :priority)
end
it "gracefully handles LLM errors" do
allow_any_instance_of(RubyLLM::Chat).to receive(:complete).and_raise("API error")
result = service.call
expect(result.success?).to be true
expect(result.data).to eq([])
end
end
Integration Testing Example
it "creates roadshow sublists with location-specific items" do
# Trigger pre-creation planning flow
chat = Chat.create!(user: user, organization: org)
message = Message.create_user(chat: chat, user: user, content: "plan roadshow")
# Answer refinement questions with locations
answers = "Locations: NYC, LA, Chicago\nBudget: \$500k\nDates: June-Sept"
Message.create_user(chat: chat, user: user, content: answers)
# Process and create list
service = ChatCompletionService.new(chat, message)
result = service.call
# Verify sublists have items
expect(List.last.sub_lists.count).to eq(3)
expect(List.last.sub_lists.first.list_items.count).to be > 0
end
Performance Considerations
Latency
- Per-sublist: ~2-3 seconds (one LLM call per subdivision)
- Parallel would require async job queueing (future optimization)
- Current: Sequential (acceptable for 3-5 sublists)
Cost
- ~$0.01 per call at current rates
- Negligible for typical use (1-10 sublists per list)
- Could optimize with caching if needed
Optimization Ideas
- Parallel Generation - Use background jobs for each sublist
- Prompt Caching - Cache system prompt between calls
- Smart Aggregation - Generate items for all sublists in one LLM call
- Template Library - Pre-generate common item patterns by domain
Common Issues & Solutions
Empty Items Array
- Cause: JSON parsing failed silently
- Solution: Check logs for "JSON parse error"
- Prevention: Service returns empty array gracefully, UI shows empty sublist
Generic Items Generated
- Cause: LLM ignored sublist_title context
- Solution: Verify prompt includes sublist_title and planning_context
- Prevention: Update prompt to emphasize specificity
Slow Generation
- Cause: gpt-5.4 reasoning is slower than gpt-5-nano
- Solution: This is expected trade-off for quality
- Optimization: Consider async generation in background jobs
Future Enhancements
- Batch Processing - Generate items for multiple sublists in one call
- Template Selection - Choose prompt template based on planning_domain
- User Preferences - Customize item generation style per user
- Multi-language - Support item generation in other languages
- Item Prioritization - Automatically set priority based on domain rules
- Dependency Tracking - Identify item dependencies within sublists
Related Services
- ListRefinementService - Generates clarifying questions before item generation
- CombinedIntentComplexityService - Detects if list is "complex" and needs refinement
- ListCreationService - Creates lists and sublists with generated items
- ChatCompletionService - Orchestrates the entire flow
Files
- Implementation:
app/services/item_generation_service.rb - Usage:
app/services/chat_completion_service.rb(line 1018-1070) - Tests:
spec/services/item_generation_service_spec.rb(when created)