# RISK #7: Fixing Plan — No Timeout for LLM Calls

**Risk ID:** RISK-007  
**Severity:** 🟡 MEDIUM — Blocker untuk V3 Modularization  
**File Target:** `app/services/ingestion.py`  
**Date:** 2026-05-12  
**Status:** PLANNING  

---

## 1. Root Cause

Ada **3 LLM API call** di `ingestion.py` yang tidak memiliki parameter `timeout`:

| # | Fungsi | Line | API |
|---|--------|------|-----|
| 1 | `extract_pillars_with_llm` | ~294 | DashScope (Qwen) |
| 2 | `embed_text` | ~371 | OpenAI Embeddings |
| 3 | `generate_edge_reasoning` | ~781 | DashScope (Qwen) |

```python
# Kondisi saat ini — TIDAK ADA timeout
with semaphore:
    response = client.chat.completions.create(
        model=model_name,
        response_format={"type": "json_object"},
        messages=[...],
        temperature=0.1
        # ← timeout tidak ada
    )
```

Jika API hang (network issue, server overload, rate limit tanpa response), script **menunggu selamanya** tanpa bisa recover.

---

## 2. Dampak

**Development:**
- Script hang tanpa output — developer harus Ctrl+C manual
- Tidak ada pesan error yang jelas

**Production:**
- Thread di `ThreadPoolExecutor` terkunci selamanya
- Semaphore tidak pernah di-release → thread lain ikut terkunci
- Pipeline berhenti total tanpa recovery

**Relevansi ke V3:**
- `extract_pillars_with_llm` akan dipindah ke `llm_extractor.py`
- Jika dipindah tanpa timeout, masalah ini terbawa ke modul baru

---

## 3. Solusi: Parameter `timeout`

OpenAI SDK (yang digunakan untuk DashScope compatible mode) mendukung parameter `timeout` langsung di `create()`:

```python
# SETELAH FIX
with semaphore:
    response = client.chat.completions.create(
        model=model_name,
        response_format={"type": "json_object"},
        messages=[...],
        temperature=0.1,
        timeout=60.0  # ← 60 detik maksimum
    )
```

Jika timeout terlampaui, SDK akan raise `openai.APITimeoutError` yang sudah di-handle oleh `@retry` decorator (tenacity) yang sudah ada.

---

## 4. Nilai Timeout yang Dipilih

| Fungsi | Timeout | Alasan |
|--------|---------|--------|
| `extract_pillars_with_llm` | `60.0` detik | LLM extraction kompleks, butuh waktu lebih |
| `embed_text` | `30.0` detik | Embedding sederhana, seharusnya cepat |
| `generate_edge_reasoning` | `60.0` detik | LLM reasoning, butuh waktu lebih |

Nilai ini berdasarkan observasi dari run faktual:
- LLM call rata-rata: 12-44 detik
- Timeout 60 detik = 2x rata-rata maksimum → cukup longgar tapi tetap ada batas

---

## 5. Langkah Implementasi

| # | Langkah | Target | Detail |
|---|---------|--------|--------|
| 1 | Tambah `timeout=60.0` di `extract_pillars_with_llm` | line ~294 | DashScope Qwen call |
| 2 | Tambah `timeout=30.0` di `embed_text` | line ~371 | OpenAI Embeddings call |
| 3 | Tambah `timeout=60.0` di `generate_edge_reasoning` | line ~781 | DashScope Qwen call |
| 4 | Verify syntax `py_compile` | — | Tidak ada syntax error |
| 5 | Smoke test | — | Verify parameter ada di ketiga call |

---

## 6. Yang TIDAK Diubah

- ❌ Tidak mengubah nilai `@retry` decorator (tenacity sudah handle `APITimeoutError`)
- ❌ Tidak mengubah logic prompt atau extraction
- ❌ Tidak mengubah semaphore logic

---

## 7. Acceptance Criteria

- [ ] `extract_pillars_with_llm` punya `timeout=60.0`
- [ ] `embed_text` punya `timeout=30.0`
- [ ] `generate_edge_reasoning` punya `timeout=60.0`
- [ ] `python3 -m py_compile ingestion.py` → PASS
- [ ] Smoke test grep konfirmasi ketiga timeout ada

---

## 8. Estimasi Waktu

| Task | Waktu |
|------|-------|
| Tambah 3x parameter timeout | 5 menit |
| Verify + smoke test | 5 menit |
| **Total** | **~10 menit** |
