# LLM Integration

discopt includes optional LLM-powered features for model explanation, natural language formulation, infeasibility diagnosis, conversational model building, and more. These features use [litellm](https://github.com/BerriAI/litellm) as a universal adapter supporting 100+ LLM providers.

```{important}
LLM features are **purely advisory** — they never affect solver correctness. The solver runs identically with or without LLM features enabled. All formulations generated by LLMs pass through `model.validate()` before use.
```

## Installation

```bash
pip install discopt[llm]
```

This installs `litellm` as an optional dependency. All LLM features degrade gracefully when litellm is not installed.

## Configuration

### API Key Setup

Set the API key for your chosen provider via environment variables:

| Provider | Environment Variable | Model String Example |
|----------|---------------------|---------------------|
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic/claude-sonnet-4-20250514` |
| OpenAI | `OPENAI_API_KEY` | `openai/gpt-4o` |
| Google | `GEMINI_API_KEY` | `gemini/gemini-pro` |
| Ollama (local) | *(none needed)* | `ollama/llama3` |
| AWS Bedrock | `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | `bedrock/anthropic.claude-3-sonnet` |
| Azure OpenAI | `AZURE_API_KEY` + `AZURE_API_BASE` | `azure/gpt-4o` |

### Model Selection

Models are resolved in this priority order:

1. Explicit `model=` parameter on the function call
2. `DISCOPT_LLM_MODEL` environment variable
3. Default: `anthropic/claude-sonnet-4-20250514`

```bash
# Set your preferred model globally
export DISCOPT_LLM_MODEL="ollama/llama3"
export ANTHROPIC_API_KEY="sk-ant-..."
```

## Features Overview

### Result Explanation (`explain()`)

Get a rich, context-aware explanation of any solve result:

```python
result = model.solve()
print(result.explain(llm=True))
```

The explanation is status-specific:
- **Optimal**: which variables are active, which constraints are binding, solution interpretation
- **Infeasible**: which constraints likely conflict, suggested relaxations
- **Iteration limit**: convergence analysis, parameter tuning suggestions
- **Time/node limit**: B&B progress assessment, relaxation quality analysis

### Natural Language Formulation (`from_description()`)

Create models from natural language descriptions using structured tool calling (not code generation):

```python
import discopt.modeling as dm

model = dm.from_description(
    "Minimize total shipping cost from 3 warehouses to 5 customers. "
    "Each warehouse has limited supply and each customer has a demand "
    "that must be met.",
    data={
        "supply": [100, 150, 200],
        "demand": [80, 60, 70, 40, 50],
        "costs": np.random.rand(3, 5) * 100,
    },
)
```

### LLM-Enhanced Solving (`llm=True`)

Pass `llm=True` to `solve()` for pre-solve analysis and automatic result explanation:

```python
result = model.solve(llm=True)
# Pre-solve warnings are logged (advisory only, never blocks solving)
# Result explanation is automatically generated
print(result.explain())
```

### Conversational Model Building (`discopt.chat()`)

Build models interactively through a conversation:

```python
import discopt

session = discopt.chat()
session.send("I have a factory with 3 machines that can produce 2 products")
session.send("Machine capacities are [100, 150, 200] hours per week")
session.send("Product profits are [50, 80] per unit")
session.send("Solve it")
session.close()
```

### Infeasibility Diagnosis

When a model is infeasible, get actionable diagnosis:

```python
from discopt.llm.diagnosis import diagnose_infeasibility

result = model.solve()
if result.status == "infeasible":
    print(diagnose_infeasibility(model, result))
```

### Solver Strategy Advisor

Get solver parameter recommendations based on model structure:

```python
from discopt.llm.advisor import suggest_solver_params

params = suggest_solver_params(model)
print(params["reasoning"])
result = model.solve(**{k: v for k, v in params.items() if k != "reasoning"})
```

### Auto-Reformulation Analysis

Identify reformulation opportunities:

```python
from discopt.llm.reformulation import analyze_reformulations

suggestions = analyze_reformulations(model, llm=True)
for s in suggestions:
    print(f"[{s.category}] {s.description}")
    print(f"  Impact: {s.impact}")
```

## Claude Code Skill Files

The following Claude Code slash commands are available for discopt users:

| Command | Description |
|---------|-------------|
| `/formulate` | Natural language to discopt model |
| `/diagnose` | Solve result diagnostics |
| `/reformulate` | Model improvement suggestions |
| `/explain-model` | Generate mathematical documentation |
| `/convert` | Cross-solver translation (Pyomo, GAMS, AMPL, JuMP) |
| `/benchmark-report` | Analyze benchmark JSON results |

## Safety Guarantees

1. **LLM outputs never affect solver math** — commentary is decorative; formulations go through `validate()` + `solve()`
2. **Structured output over free-form code** — `from_description()` uses tool calling mapped to Model methods, not arbitrary code generation
3. **Graceful degradation** — if LLM is unavailable/slow/broken, the solver runs identically without LLM features
4. **Timeouts** — 5s for explanation, 30s for formulation. LLM calls never block the B&B loop
5. **No secrets in prompts** — model structure sent to LLM is the same info available via `model.summary()`
