# Phase 10 — LLM Integration & Smart Features

**Goal**: Wire up the `ILlmProvider` interface (defined in Phase 5) to real LLM backends. Enable all the smart features that have had placeholder endpoints throughout Phases 5-9. Add new natural language interaction capabilities for both medicine and food domains.

**Depends on**: All previous phases (feature endpoints already exist)

---

## Deliverables

1. Concrete `ILlmProvider` implementations (OpenAI, Anthropic, Ollama)
2. Config-driven provider selection
3. LLM request/response logging and cost tracking
4. Rate limiting and budget controls
5. Smart features fully wired:
   - Product recognition (photo → nutrition)
   - Receipt parsing (photo → store + items + prices)
   - Recipe import (text/URL → structured recipe)
   - Natural language pantry entry
   - LLM-powered meal plan suggestions
6. Prompt templates and versioning

---

## Architecture

### Provider Selection

```
Environment variable: LLM_PROVIDER_TYPE=openai|anthropic|ollama|noop

LlmModule registers the provider dynamically:

@Module({})
export class LlmModule {
  static forRoot(): DynamicModule {
    return {
      providers: [{
        provide: LLM_PROVIDER,
        useFactory: (config: ConfigService) => {
          switch (config.get('LLM_PROVIDER_TYPE')) {
            case 'openai':    return new OpenAiProvider(config);
            case 'anthropic': return new AnthropicProvider(config);
            case 'ollama':    return new OllamaProvider(config);
            default:          return new NoOpLlmProvider();
          }
        },
        inject: [ConfigService],
      }],
      exports: [LLM_PROVIDER],
    };
  }
}
```

### Provider Implementations

Each provider implements `ILlmProvider` and handles:

- API authentication (keys from env)
- Model selection (configurable per provider)
- Request/response mapping to/from vendor format
- Error handling and retries (exponential backoff)
- Timeout management

```typescript
// packages/api/src/modules/llm/providers/
├── noop.provider.ts          # Returns null for everything (already exists from Phase 5)
├── openai.provider.ts        # GPT-4o / GPT-4o-mini
├── anthropic.provider.ts     # Claude 3.5 Sonnet / Haiku
└── ollama.provider.ts        # Local models (Llama 3, Mistral, etc.)
```

### Environment Configuration

```env
# Provider selection
LLM_PROVIDER_TYPE=openai

# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o
OPENAI_VISION_MODEL=gpt-4o       # For image inputs

# Anthropic
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-4-20250514

# Ollama
OLLAMA_BASE_URL=http://ollama:11434
OLLAMA_MODEL=llama3
OLLAMA_VISION_MODEL=llava

# Budget
LLM_MONTHLY_BUDGET_USD=20.00
LLM_RATE_LIMIT_RPM=30             # Requests per minute per household
```

---

## Tasks

### 10.1 — OpenAI Provider

```typescript
class OpenAiProvider implements ILlmProvider {
  // Uses OpenAI Node.js SDK
  // Text endpoints: chat completions with JSON mode
  // Vision endpoints: chat completions with image_url or base64 content
  // Structured output: use function calling or response_format: json_schema
}
```

- `extractNutrition`: Send product text/image → prompt asks for structured nutrition JSON
- `parseRecipe`: Send recipe text → prompt extracts name, servings, ingredients[], steps[]
- `parseRecipeFromUrl`: Fetch URL content first, then parse as text
- `parseReceipt`: Send receipt image → prompt extracts store, date, line items with prices
- `suggestMealPlan`: Send pantry summary + targets + preferences → get 7-day plan
- `parseNaturalLanguage`: Send user text → extract intent + entities (add product, log purchase, etc.)

### 10.2 — Anthropic Provider

```typescript
class AnthropicProvider implements ILlmProvider {
  // Uses Anthropic SDK
  // Similar structure to OpenAI but with Claude-specific API format
  // Vision: send image as base64 in messages
  // Structured output: use tool_use for JSON extraction
}
```

### 10.3 — Ollama Provider

```typescript
class OllamaProvider implements ILlmProvider {
  // Uses Ollama REST API (http://host:11434/api/generate or /api/chat)
  // Text: standard chat endpoint
  // Vision: requires multimodal model (llava, bakllava)
  // Note: local models may be less accurate — adjust prompts for simpler output
  // No cost tracking needed (self-hosted)
}
```

### 10.4 — Prompt Templates

Create versioned prompt templates in `packages/api/src/modules/llm/prompts/`:

```typescript
// prompts/extract-nutrition.ts
export const EXTRACT_NUTRITION_PROMPT = {
  version: '1.0',
  system: `You are a nutrition data extraction assistant. Given a food product description or image, extract nutritional information. Return ONLY valid JSON matching the schema below. If you cannot determine a value, use null. Be conservative with estimates.`,
  schema: {
    name: 'string',
    brand: 'string | null',
    servingSize: 'number',
    servingUnit: 'g | ml | oz | piece',
    nutrition: {
      calories: 'number',
      protein: 'number (grams)',
      carbs: 'number (grams)',
      fat: 'number (grams)',
      fiber: 'number | null',
      sugar: 'number | null',
      sodium: 'number | null (mg)',
    },
  },
};

// prompts/parse-recipe.ts
// prompts/parse-receipt.ts
// prompts/suggest-meal-plan.ts
// prompts/parse-natural-language.ts
```

- Each prompt has a `version` for tracking which prompt produced which results
- Prompts are provider-agnostic (providers may wrap them differently)

### 10.5 — LLM Logging & Cost Tracking

```typescript
// Schema
export interface LlmLog {
  id: string;
  householdId: string;
  userId: string;
  provider: string; // 'openai' | 'anthropic' | 'ollama'
  model: string;
  feature: string; // 'extract_nutrition' | 'parse_recipe' | etc.
  promptVersion: string;
  inputTokens: number;
  outputTokens: number;
  totalTokens: number;
  costUsd: number; // Computed from token count + model pricing
  latencyMs: number;
  success: boolean;
  errorMessage?: string;
  createdAt: Date;
}
```

- All provider calls are wrapped in a logging decorator/interceptor
- Cost computed from known per-model pricing (configurable)
- Monthly cost aggregation endpoint for household admins

### 10.6 — Rate Limiting & Budget Controls

```typescript
class LlmBudgetGuard {
  /**
   * Before each LLM call:
   * 1. Check per-household rate limit (requests per minute)
   * 2. Check monthly budget: sum costUsd for current month vs LLM_MONTHLY_BUDGET_USD
   * 3. If exceeded, throw BudgetExceededException (HTTP 429)
   */
  async canProceed(householdId: string): Promise<boolean>;
}
```

- Rate limiting: Redis-backed or in-memory (household-level RPM)
- Budget: MongoDB aggregation on `LlmLog` collection

### 10.7 — Wire Up All Feature Endpoints

Each of these endpoints already exists as a placeholder from earlier phases. Now they get real LLM calls:

| Feature               | Endpoint (existing)                 | Phase | LLM Method             |
| --------------------- | ----------------------------------- | ----- | ---------------------- |
| Product recognition   | `POST /products/smart-add`          | 5     | `extractNutrition()`   |
| Recipe import (text)  | `POST /recipes/import-text`         | 6     | `parseRecipe()`        |
| Recipe import (URL)   | `POST /recipes/import-url`          | 6     | `parseRecipeFromUrl()` |
| Receipt parsing       | `POST /prices/parse-receipt`        | 9     | `parseReceipt()`       |
| Meal plan suggestions | `POST /meal-plans/suggest-with-llm` | 8     | `suggestMealPlan()`    |

### 10.8 — Natural Language Input (New Feature)

New universal endpoint:

```
POST /api/v1/nlp/parse
Body: { text: string }
Response: { intent: string, action: StructuredAction, confidence: number }
```

Supported intents:

- `add_pantry_item`: "I bought 2 lbs of chicken at Costco for $12" → create pantry item + price record
- `add_product`: "Add whole milk, 240ml serving, 150 cal, 8g protein, 12g carbs, 8g fat" → create product
- `check_expiry`: "What's expiring this week?" → redirect to pantry query
- `find_recipe`: "What can I make with chicken and rice?" → trigger suggestion engine
- `add_to_list`: "Add eggs and butter to my shopping list" → add items to active list

Each recognized intent maps to an existing API operation, executed automatically or returned as a confirmation prompt.

### 10.9 — Web UI: LLM Settings & Features

- `/settings/llm` page (admin only):
  - Provider selection display
  - Monthly cost usage bar
  - Rate limit configuration
  - LLM log viewer (recent calls, success/failure, latency, cost)
- Enhance existing UI with LLM-powered features:
  - Product add modal: "Smart Add" tab with camera/text → LLM pre-fill
  - Recipe page: "Import from text" and "Import from URL" now functional
  - Shopping list: "Scan receipt" button with camera
  - Dashboard: natural language input bar ("What should I cook tonight?")

### 10.10 — Docker: Ollama Service (Optional)

If user wants local LLM, add to Docker Compose:

```yaml
ollama:
  image: ollama/ollama
  ports: ['11434:11434']
  volumes: [ollama-models:/root/.ollama]
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            count: all
            capabilities: [gpu]
  profiles: [llm-local]
```

---

## Acceptance Criteria

- [ ] Can switch LLM provider via environment variable
- [ ] OpenAI provider successfully extracts nutrition from product photo
- [ ] Recipe import from text returns a valid structured recipe
- [ ] Receipt parsing extracts store, items, and prices from receipt image
- [ ] All LLM calls are logged with token counts and cost
- [ ] Rate limiting prevents exceeding configured RPM
- [ ] Monthly budget guard blocks calls when budget is exceeded
- [ ] Natural language input correctly identifies intents and executes actions
- [ ] NoOp provider still works gracefully when no LLM is configured
- [ ] LLM settings page shows usage and cost statistics

---

## Estimated Effort

Large. Multiple provider implementations, prompt engineering, testing across different models, cost tracking infrastructure, and NLP intent parsing are all significant.

---

## Notes

- Prompt engineering is iterative — expect to refine prompts based on real-world testing
- Different providers/models will have varying accuracy — consider model-specific prompt tuning
- Local models (Ollama) will be less accurate but free — document quality trade-offs
- Consider caching LLM results for identical inputs (e.g., same barcode photo → same product)
