TraceMind / screens /settings.py
kshitijthakkar's picture
docs: Add comprehensive job submission requirements
4449927
"""
Settings Screen for TraceMind-AI
Allows users to configure API keys for Gemini, HuggingFace, Modal, and LLM providers
"""
import gradio as gr
import os
# Note: Removed _get_llm_keys_as_env_string() to prevent exposing environment variables
# in the UI for security reasons. Users should explicitly enter keys needed for jobs.
def _parse_env_string(env_string):
"""
Parse ENV-formatted string into a dictionary
Args:
env_string: Multi-line string in KEY=value format
Returns:
dict: Parsed key-value pairs
"""
result = {}
if not env_string:
return result
for line in env_string.strip().split("\n"):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, value = line.split("=", 1)
result[key.strip()] = value.strip()
return result
def create_settings_screen():
"""
Create the settings screen for API key configuration
Returns:
gr.Column: Gradio Column component for settings (can be shown/hidden)
"""
with gr.Column(visible=False) as settings_interface:
gr.Markdown("""
# ⚙️ Settings
Configure your API keys to use TraceMind features. These keys are stored only in your browser session and are never saved to our servers.
""")
with gr.Accordion("🔑 API Key Configuration", open=True):
gr.Markdown("""
### Why provide API keys?
TraceMind uses external services to provide intelligent analysis and insights:
- **Google Gemini API**: Powers the MCP server for leaderboard analysis, cost estimation, and trace debugging
- **HuggingFace Token**: Required to access evaluation datasets and results
**For Judges & Visitors**: Please enter your own API keys to prevent credit issues during evaluation.
""")
# Gemini API Key
with gr.Row():
with gr.Column(scale=4):
gemini_api_key = gr.Textbox(
label="Google Gemini API Key",
placeholder="Enter your Gemini API key (starts with 'AIza...')",
type="password",
value=os.environ.get("GEMINI_API_KEY", ""),
info="Get your free API key at: https://ai.google.dev/"
)
with gr.Column(scale=1):
gemini_status = gr.Markdown("⚪ Not configured")
# HuggingFace Token
with gr.Row():
with gr.Column(scale=4):
hf_token = gr.Textbox(
label="HuggingFace Token (Required for Job Submission)",
placeholder="Enter your HF token (starts with 'hf_...')",
type="password",
value=os.environ.get("HF_TOKEN", ""),
info="⚠️ Token needs: Read + Write + Run Jobs permissions | Pro account required"
)
with gr.Column(scale=1):
hf_status = gr.Markdown("⚪ Not configured")
# Modal API Key
with gr.Row():
with gr.Column(scale=4):
modal_api_key = gr.Textbox(
label="Modal API Key (Optional)",
placeholder="Enter your Modal API key (starts with 'ak-...')",
type="password",
value=os.environ.get("MODAL_TOKEN_ID", ""),
info="Get your key at: https://modal.com/settings/tokens"
)
with gr.Column(scale=1):
modal_status = gr.Markdown("⚪ Not configured")
# Modal API Secret
with gr.Row():
with gr.Column(scale=4):
modal_api_secret = gr.Textbox(
label="Modal API Secret (Optional)",
placeholder="Enter your Modal API secret (starts with 'as-...')",
type="password",
value=os.environ.get("MODAL_TOKEN_SECRET", ""),
info="Required if using Modal for job execution"
)
with gr.Column(scale=1):
modal_secret_status = gr.Markdown("⚪ Not configured")
# LLM Provider API Keys (Multi-line for convenience)
gr.Markdown("""
### LLM Provider API Keys (Optional)
Paste your API keys in ENV format below. These are needed for running evaluations with API-based models.
""")
llm_api_keys = gr.Textbox(
label="LLM Provider API Keys",
placeholder="""OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=AIza...
GEMINI_API_KEY=AIza...
COHERE_API_KEY=...
MISTRAL_API_KEY=...
TOGETHER_API_KEY=...
GROQ_API_KEY=gsk_...
REPLICATE_API_TOKEN=r8_...
ANYSCALE_API_KEY=...
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-west-2
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_ENDPOINT=https://...
LITELLM_API_KEY=...""",
lines=10,
value="", # Don't expose existing env vars
info="Enter one key=value per line. These will be passed to evaluation jobs."
)
# Save button
with gr.Row():
save_btn = gr.Button("💾 Save API Keys", variant="primary")
test_btn = gr.Button("🧪 Test Connection", variant="secondary")
# Status message
status_message = gr.Markdown("")
with gr.Accordion("📖 How to Get API Keys", open=False):
gr.Markdown("""
### Google Gemini API Key
1. Go to [Google AI Studio](https://ai.google.dev/)
2. Click "Get API Key" in the top right
3. Create a new project or select an existing one
4. Generate an API key
5. Copy the key (starts with `AIza...`)
**Free Tier**: 60 requests per minute, suitable for testing and demos
---
### HuggingFace Token
**For Job Submission (Required):**
1. Go to [HuggingFace Settings](https://huggingface.co/settings/tokens)
2. Click "New token"
3. Give it a name (e.g., "TraceMind Job Submission")
4. Select these permissions:
- ✅ **Read** (view datasets)
- ✅ **Write** (upload results)
- ✅ **Run Jobs** (submit evaluation jobs)
5. Create and copy the token (starts with `hf_...`)
**⚠️ IMPORTANT Requirements:**
- You must have a **HuggingFace Pro account** ($9/month)
- **Credit card required** to pay for compute usage
- Read-only tokens will NOT work for job submission
- Sign up for Pro: https://huggingface.co/pricing
---
### Modal API Credentials (Optional)
1. Go to [Modal Settings](https://modal.com/settings/tokens)
2. Click "Create new token"
3. Copy both:
- Token ID (starts with `ak-...`)
- Token Secret (starts with `as-...`)
**Why Modal?** Run evaluation jobs on serverless GPU compute with per-second billing.
---
### LLM Provider API Keys (Optional)
These keys enable running evaluations with different model providers:
- **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
- **Anthropic**: [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)
- **Google (Vertex AI)**: [Google Cloud Console](https://console.cloud.google.com/)
- **Cohere**: [dashboard.cohere.ai/api-keys](https://dashboard.cohere.ai/api-keys)
- **Mistral**: [console.mistral.ai/api-keys](https://console.mistral.ai/api-keys)
- **Together AI**: [api.together.xyz/settings/api-keys](https://api.together.xyz/settings/api-keys)
- **Groq**: [console.groq.com/keys](https://console.groq.com/keys)
Copy and paste them in the `KEY=value` format.
""")
with gr.Accordion("🔒 Privacy & Security", open=False):
gr.Markdown("""
### Your Privacy Matters
- ✅ **Session-only storage**: API keys are stored only in your browser session
- ✅ **No server storage**: Keys are never saved to our servers or databases
- ✅ **HTTPS encryption**: All API calls are made over secure connections
- ✅ **No logging**: API keys are not logged or tracked
### Best Practices
- 🔐 Use dedicated API keys for testing/demos
- 🔄 Rotate your keys regularly
- 🚫 Don't share your keys publicly
- 📊 Monitor your API usage on provider dashboards
### Rate Limits
**Gemini API (Free Tier)**:
- 60 requests per minute
- 1,500 requests per day
**HuggingFace**:
- Read access: No strict limits
- Public datasets: Unlimited reads
""")
# Define save functionality
def save_api_keys(gemini_key, hf_key, modal_key, modal_secret, llm_keys_text):
"""Save API keys to session"""
messages = []
# Validate and save Gemini API key
if gemini_key and gemini_key.strip():
if gemini_key.startswith("AIza"):
os.environ["GEMINI_API_KEY"] = gemini_key.strip()
messages.append("✅ Gemini API key saved")
gemini_status_text = "✅ Configured"
else:
messages.append("⚠️ Invalid Gemini API key format (should start with 'AIza')")
gemini_status_text = "❌ Invalid format"
else:
gemini_status_text = "⚪ Not configured"
# Validate and save HuggingFace token
if hf_key and hf_key.strip():
if hf_key.startswith("hf_"):
os.environ["HF_TOKEN"] = hf_key.strip()
messages.append("✅ HuggingFace token saved")
hf_status_text = "✅ Configured"
else:
messages.append("⚠️ Invalid HuggingFace token format (should start with 'hf_')")
hf_status_text = "❌ Invalid format"
else:
hf_status_text = "⚪ Not configured"
# Validate and save Modal API key
if modal_key and modal_key.strip():
if modal_key.startswith("ak-"):
os.environ["MODAL_TOKEN_ID"] = modal_key.strip()
messages.append("✅ Modal API key saved")
modal_status_text = "✅ Configured"
else:
messages.append("⚠️ Invalid Modal API key format (should start with 'ak-')")
modal_status_text = "❌ Invalid format"
else:
modal_status_text = "⚪ Not configured"
# Validate and save Modal API secret
if modal_secret and modal_secret.strip():
if modal_secret.startswith("as-"):
os.environ["MODAL_TOKEN_SECRET"] = modal_secret.strip()
messages.append("✅ Modal API secret saved")
modal_secret_status_text = "✅ Configured"
else:
messages.append("⚠️ Invalid Modal API secret format (should start with 'as-')")
modal_secret_status_text = "❌ Invalid format"
else:
modal_secret_status_text = "⚪ Not configured"
# Parse and save LLM provider API keys
llm_keys_count = 0
if llm_keys_text and llm_keys_text.strip():
parsed_keys = _parse_env_string(llm_keys_text)
for key, value in parsed_keys.items():
os.environ[key] = value
llm_keys_count += 1
messages.append(f"✅ {llm_keys_count} LLM provider API key(s) saved")
status_msg = "\n\n".join(messages) if messages else "No changes made"
status_msg += "\n\n**Note**: Keys are saved for this session only and will be used for evaluation jobs."
return status_msg, gemini_status_text, hf_status_text, modal_status_text, modal_secret_status_text
def test_api_keys(gemini_key, hf_key, modal_key, modal_secret, llm_keys_text):
"""Test API key connections"""
results = []
# Test Gemini API
if gemini_key and gemini_key.strip():
try:
import google.generativeai as genai
genai.configure(api_key=gemini_key.strip())
# Try to list models as a test
models = list(genai.list_models())
results.append("✅ **Gemini API**: Connection successful!")
except Exception as e:
results.append(f"❌ **Gemini API**: Connection failed - {str(e)}")
else:
results.append("⚠️ **Gemini API**: No key provided")
# Test HuggingFace token
if hf_key and hf_key.strip():
try:
from huggingface_hub import HfApi
api = HfApi(token=hf_key.strip())
# Try to get user info as a test
user_info = api.whoami()
results.append(f"✅ **HuggingFace**: Connection successful! (User: {user_info['name']})")
except Exception as e:
results.append(f"❌ **HuggingFace**: Connection failed - {str(e)}")
else:
results.append("⚠️ **HuggingFace**: No token provided")
# Test Modal API
if modal_key and modal_key.strip() and modal_secret and modal_secret.strip():
try:
import modal
# Modal validates credentials on first use, not at import
# We'll just validate format here
if modal_key.startswith("ak-") and modal_secret.startswith("as-"):
results.append("✅ **Modal**: Credentials format valid (will be verified on first job submission)")
else:
results.append("❌ **Modal**: Invalid credential format")
except Exception as e:
results.append(f"⚠️ **Modal**: {str(e)}")
elif modal_key or modal_secret:
results.append("⚠️ **Modal**: Both API key and secret required")
# Note about LLM provider keys
if llm_keys_text and llm_keys_text.strip():
parsed_keys = _parse_env_string(llm_keys_text)
results.append(f"ℹ️ **LLM Providers**: {len(parsed_keys)} key(s) configured (will be validated when used)")
return "\n\n".join(results)
# Wire up button events (api_name=False to prevent API key exposure)
save_btn.click(
fn=save_api_keys,
inputs=[gemini_api_key, hf_token, modal_api_key, modal_api_secret, llm_api_keys],
outputs=[status_message, gemini_status, hf_status, modal_status, modal_secret_status],
api_name=False # IMPORTANT: Prevents API key exposure via Gradio API
)
test_btn.click(
fn=test_api_keys,
inputs=[gemini_api_key, hf_token, modal_api_key, modal_api_secret, llm_api_keys],
outputs=[status_message],
api_name=False # IMPORTANT: Prevents API key exposure via Gradio API
)
# Return the interface only (API keys are managed internally via session state)
return settings_interface
if __name__ == "__main__":
# For standalone testing
with gr.Blocks() as demo:
settings_screen = create_settings_screen()
# Make it visible for standalone testing
settings_screen.visible = True
demo.launch()