File size: 9,642 Bytes
b866967
dfdeb30
 
 
 
b866967
dfdeb30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b866967
 
 
 
dfdeb30
 
 
 
 
 
b866967
dfdeb30
 
 
 
 
 
 
b866967
dfdeb30
 
 
 
 
 
 
 
 
 
b866967
dfdeb30
 
b866967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdeb30
b866967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdeb30
 
b866967
dfdeb30
 
 
 
 
 
 
 
b866967
dfdeb30
 
 
 
 
 
 
 
 
 
 
b866967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdeb30
 
 
 
 
b866967
 
 
dfdeb30
 
b866967
dfdeb30
 
 
 
b866967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdeb30
 
 
b866967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdeb30
 
b866967
dfdeb30
b866967
dfdeb30
b866967
dfdeb30
b866967
 
 
 
dfdeb30
 
b866967
dfdeb30
 
b866967
dfdeb30
 
 
 
 
b866967
dfdeb30
 
 
b866967
 
 
dfdeb30
b866967
dfdeb30
 
 
b866967
dfdeb30
b866967
dfdeb30
b866967
dfdeb30
 
 
 
 
b866967
dfdeb30
 
 
 
 
b866967
dfdeb30
b866967
dfdeb30
 
b866967
dfdeb30
 
b866967
 
 
 
 
dfdeb30
 
 
 
b866967
dfdeb30
 
b866967
 
 
 
 
 
 
 
dfdeb30
 
b866967
dfdeb30
 
 
 
 
b866967
 
 
 
 
 
dfdeb30
b866967
dfdeb30
b866967
 
 
 
 
 
 
 
 
 
dfdeb30
 
 
 
 
 
b866967
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# UI Simplification: Remove API Provider Dropdown

**Issues**: #52, #53
**Priority**: P1 - UX improvement for hackathon demo
**Estimated Time**: 30 minutes
**Senior Review**: βœ… Approved with changes (incorporated below)

---

## Problem

The current UI has confusing BYOK (Bring Your Own Key) settings:

1. **Provider dropdown is misleading** - Shows "openai" but actually uses free tier when no key
2. **Examples table shows useless columns** - API Key (empty), Provider (ignored)
3. **Anthropic doesn't work with Advanced mode** - Only OpenAI has `agent-framework` support

## Solution

Remove `api_provider` dropdown entirely. Auto-detect provider from key prefix.

**Functionality preserved:**
- Simple mode: Free tier, OpenAI, OR Anthropic (all work)
- Advanced mode: OpenAI only (Magentic multi-agent requires `OpenAIChatClient`)

---

## Implementation

### File: `src/app.py`

#### Change 1: Update `configure_orchestrator()` signature (lines 23-28)

```python
# BEFORE
def configure_orchestrator(
    use_mock: bool = False,
    mode: str = "simple",
    user_api_key: str | None = None,
    api_provider: str = "openai",  # ← REMOVE
) -> tuple[Any, str]:

# AFTER
def configure_orchestrator(
    use_mock: bool = False,
    mode: str = "simple",
    user_api_key: str | None = None,
) -> tuple[Any, str]:
```

#### Change 2: Update docstring (lines 29-40)

```python
# AFTER
    """
    Create an orchestrator instance.

    Args:
        use_mock: If True, use MockJudgeHandler (no API key needed)
        mode: Orchestrator mode ("simple" or "advanced")
        user_api_key: Optional user-provided API key (BYOK) - auto-detects provider

    Returns:
        Tuple of (Orchestrator instance, backend_name)
    """
```

#### Change 3: Replace provider logic with auto-detection (lines 62-88)

```python
# BEFORE (lines 62-88) - complex provider checking with api_provider param

# AFTER - auto-detect from key prefix
    # 2. Paid API Key (User provided or Env)
    elif user_api_key and user_api_key.strip():
        # Auto-detect provider from key prefix
        model: AnthropicModel | OpenAIModel
        if user_api_key.startswith("sk-ant-"):
            # Anthropic key
            anthropic_provider = AnthropicProvider(api_key=user_api_key)
            model = AnthropicModel(settings.anthropic_model, provider=anthropic_provider)
            backend_info = "Paid API (Anthropic)"
        elif user_api_key.startswith("sk-"):
            # OpenAI key
            openai_provider = OpenAIProvider(api_key=user_api_key)
            model = OpenAIModel(settings.openai_model, provider=openai_provider)
            backend_info = "Paid API (OpenAI)"
        else:
            raise ValueError(
                "Invalid API key format. Expected sk-... (OpenAI) or sk-ant-... (Anthropic)"
            )
        judge_handler = JudgeHandler(model=model)

    # 3. Environment API Keys (fallback)
    elif os.getenv("OPENAI_API_KEY"):
        judge_handler = JudgeHandler(model=None)  # Uses env key
        backend_info = "Paid API (OpenAI from env)"

    elif os.getenv("ANTHROPIC_API_KEY"):
        judge_handler = JudgeHandler(model=None)  # Uses env key
        backend_info = "Paid API (Anthropic from env)"

    # 4. Free Tier (HuggingFace Inference)
    else:
        judge_handler = HFInferenceJudgeHandler()
        backend_info = "Free Tier (Llama 3.1 / Mistral)"
```

#### Change 4: Update `research_agent()` signature (lines 105-111)

```python
# BEFORE
async def research_agent(
    message: str,
    history: list[dict[str, Any]],
    mode: str = "simple",
    api_key: str = "",
    api_provider: str = "openai",  # ← REMOVE
) -> AsyncGenerator[str, None]:

# AFTER
async def research_agent(
    message: str,
    history: list[dict[str, Any]],
    mode: str = "simple",
    api_key: str = "",
) -> AsyncGenerator[str, None]:
```

#### Change 5: Update docstring (lines 112-124)

```python
# AFTER
    """
    Gradio chat function that runs the research agent.

    Args:
        message: User's research question
        history: Chat history (Gradio format)
        mode: Orchestrator mode ("simple" or "advanced")
        api_key: Optional user-provided API key (BYOK - auto-detects provider)

    Yields:
        Markdown-formatted responses for streaming
    """
```

#### Change 6: Fix Advanced mode check (line 139)

```python
# BEFORE
if mode == "advanced" and not (has_openai or (has_user_key and api_provider == "openai")):

# AFTER - auto-detect OpenAI key from prefix
is_openai_user_key = user_api_key and user_api_key.startswith("sk-") and not user_api_key.startswith("sk-ant-")
if mode == "advanced" and not (has_openai or is_openai_user_key):
    yield (
        "⚠️ **Advanced mode requires OpenAI API key.** "
        "Anthropic keys only work in Simple mode. Falling back to Simple.\n\n"
    )
    mode = "simple"
```

#### Change 7: Remove premature "Using your key" message (lines 146-151)

```python
# BEFORE - uses api_provider which no longer exists
if has_user_key:
    yield (
        f"πŸ”‘ **Using your {api_provider.upper()} API key** - "
        "Your key is used only for this session and is never stored.\n\n"
    )

# AFTER - remove this block entirely
# The backend_name from configure_orchestrator already shows "Paid API (OpenAI)" or "Paid API (Anthropic)"
# No need for duplicate messaging
```

#### Change 8: Update configure_orchestrator call (lines 165-170)

```python
# BEFORE
orchestrator, backend_name = configure_orchestrator(
    use_mock=False,
    mode=mode,
    user_api_key=user_api_key,
    api_provider=api_provider,  # ← REMOVE
)

# AFTER
orchestrator, backend_name = configure_orchestrator(
    use_mock=False,
    mode=mode,
    user_api_key=user_api_key,
)
```

#### Change 9: Simplify examples (lines 210-229)

```python
# BEFORE - 4 items per example
examples=[
    ["What drugs improve female libido post-menopause?", "simple", "", "openai"],
    ["Clinical trials for erectile dysfunction alternatives to PDE5 inhibitors?", "simple", "", "openai"],
    ["Evidence for testosterone therapy in women with HSDD?", "simple", "", "openai"],
],

# AFTER - 2 items per example (query, mode) - API key always empty in examples
examples=[
    ["What drugs improve female libido post-menopause?", "simple"],
    ["Clinical trials for ED alternatives to PDE5 inhibitors?", "simple"],
    ["Evidence for testosterone therapy in women with HSDD?", "simple"],
],
```

#### Change 10: Update additional_inputs (lines 231-252)

```python
# BEFORE - 3 inputs (mode, api_key, api_provider)
additional_inputs=[
    gr.Radio(
        choices=["simple", "advanced"],
        value="simple",
        label="Orchestrator Mode",
        info="Simple: Linear (Free Tier Friendly) | Advanced: Multi-Agent (Requires OpenAI)",
    ),
    gr.Textbox(
        label="πŸ”‘ API Key (Optional - BYOK)",
        placeholder="sk-... or sk-ant-...",
        type="password",
        info="Enter your own API key. Never stored.",
    ),
    gr.Radio(  # ← REMOVE THIS ENTIRE BLOCK
        choices=["openai", "anthropic"],
        value="openai",
        label="API Provider",
        info="Select the provider for your API key",
    ),
],

# AFTER - 2 inputs (mode, api_key)
additional_inputs=[
    gr.Radio(
        choices=["simple", "advanced"],
        value="simple",
        label="Orchestrator Mode",
        info="Simple: Works with any key or free tier | Advanced: Requires OpenAI key",
    ),
    gr.Textbox(
        label="πŸ”‘ API Key (Optional)",
        placeholder="sk-... (OpenAI) or sk-ant-... (Anthropic)",
        type="password",
        info="Leave empty for free tier. Auto-detects provider from key prefix.",
    ),
],
```

#### Change 11: Update accordion label (line 230)

```python
# BEFORE
additional_inputs_accordion=gr.Accordion(label="βš™οΈ Settings", open=False),

# AFTER
additional_inputs_accordion=gr.Accordion(label="βš™οΈ Settings (Free tier works without API key)", open=False),
```

---

## Testing Checklist

### Manual Tests
- [ ] **No key**: Shows "Free Tier (Llama 3.1 / Mistral)" in backend
- [ ] **OpenAI key (sk-...)**: Shows "Paid API (OpenAI)" in backend
- [ ] **Anthropic key (sk-ant-...)**: Shows "Paid API (Anthropic)" in backend
- [ ] **Invalid key format**: Shows error message
- [ ] **Anthropic key + Advanced mode**: Falls back to Simple with warning
- [ ] **OpenAI key + Advanced mode**: Uses full Magentic multi-agent
- [ ] **Examples table**: Shows only 2 columns (query, mode)
- [ ] **MCP server**: Still accessible at `/gradio_api/mcp/`

### Unit Test Updates
- [ ] `tests/unit/test_app_smoke.py` - may need update if checking input count

---

## Definition of Done

- [ ] `api_provider` parameter removed from `configure_orchestrator()`
- [ ] `api_provider` parameter removed from `research_agent()`
- [ ] Auto-detection logic works for `sk-` and `sk-ant-` prefixes
- [ ] Advanced mode check uses auto-detection (not removed param)
- [ ] "Using your X key" message removed (backend_name handles this)
- [ ] Examples table shows 2 columns
- [ ] Accordion label updated
- [ ] Placeholder text shows both key formats
- [ ] All existing tests pass
- [ ] MCP server still works

---

## Mode Compatibility Matrix (Unchanged)

| Mode | No Key | OpenAI Key | Anthropic Key |
|------|--------|------------|---------------|
| **Simple** | βœ… Free tier | βœ… GPT-5.1 | βœ… Claude Sonnet 4.5 |
| **Advanced** | ⚠️ Falls back | βœ… Full Magentic | ⚠️ Falls back to Simple |

---

## Related
- Issue #52: UI Polish - Examples table confusion
- Issue #53: API Provider Simplification
- Senior Review: Approved 2025-11-28