Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Rename this file to .env and add your API keys
ANTHROPIC_API_KEY=your_anthropic_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
GOOGLE_API_KEY=your_google_api_key_here
GOOGLE_API_KEY=your_google_api_key_here
LAMBDA_API_KEY=your_lambda_api_key_here

3 changes: 2 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
API_KEYS = {
"anthropic": os.getenv("ANTHROPIC_API_KEY"),
"openai": os.getenv("OPENAI_API_KEY"),
"google": os.getenv("GOOGLE_API_KEY")
"google": os.getenv("GOOGLE_API_KEY"),
"lambda": os.getenv("LAMBDA_API_KEY"),
}

# Print diagnostic info
Expand Down
67 changes: 67 additions & 0 deletions llm/lambda_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
try:
import openai
OPENAI_AVAILABLE = True
except ImportError:
print("OpenAI library not installed. Install with: pip install openai")
OPENAI_AVAILABLE = False

from typing import List, Dict
from llm.base import LLMClient, retry_with_backoff
import asyncio
import logging

class LambdaClient(LLMClient):
def __init__(self, api_key: str):
if not OPENAI_AVAILABLE:
raise ImportError("OpenAI library not installed. Run: pip install openai")

if not api_key or api_key == "your_lambda_api_key_here":
raise ValueError("Invalid Lambda API key. Please check your .env file")

try:
# Use OpenAI-compatible client with custom base_url
self.client = openai.OpenAI(api_key=api_key, base_url="https://api.lambda.ai/v1")
logging.info("Lambda (OpenAI-compatible) client initialized successfully")
except Exception as e:
logging.error(f"Failed to initialize Lambda client: {e}")
raise

async def generate_response(
self,
system_prompt: str,
messages: List[Dict],
temperature: float = 0.7,
max_tokens: int = 2048
) -> str:
async def _generate():
try:
messages_formatted = [{"role": "system", "content": system_prompt}] + messages

# For OpenAI-compatible chat.completions, use max_tokens
response = await asyncio.to_thread(
self.client.chat.completions.create,
model="deepseek-llama3.3-70b",
messages=messages_formatted,
temperature=temperature,
max_tokens=max_tokens
)

if response and getattr(response, 'choices', None):
first = response.choices[0]
# Some providers may return "message" or "delta"
if hasattr(first, 'message') and first.message:
return first.message.content
elif hasattr(first, 'text') and first.text:
return first.text
else:
raise ValueError("Unexpected response format from Lambda API")
else:
raise ValueError("Empty response from Lambda API")
except openai.APIError as e:
logging.error(f"Lambda API (OpenAI-compatible) error: {e}")
raise
except Exception as e:
logging.error(f"Unexpected error calling Lambda API: {e}")
raise

return await retry_with_backoff(_generate)
7 changes: 5 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from llm.anthropic_client import ClaudeClient
from llm.openai_client import GPTClient
from llm.google_client import GeminiClient
from llm.lambda_client import LambdaClient
from moderator.turn_manager import TurnManager
from ui.terminal import TerminalUI
from storage.session_logger import SessionLogger
Expand All @@ -29,7 +30,8 @@ def __init__(self):
"claude_moderator": ClaudeClient(API_KEYS["anthropic"]),
"claude": ClaudeClient(API_KEYS["anthropic"]),
"gpt5": GPTClient(API_KEYS["openai"]),
"gemini": GeminiClient(API_KEYS["google"])
"gemini": GeminiClient(API_KEYS["google"]),
"deepseek": LambdaClient(API_KEYS["lambda"]),
}
except Exception as e:
self.ui.console.print(f"[red]Error initializing LLM clients: {e}[/red]")
Expand All @@ -40,7 +42,8 @@ def __init__(self):
"claude_moderator": "Claude 4.1 Opus",
"claude": "Claude 4.1 Opus",
"gpt5": "GPT-5 Thinking",
"gemini": "Gemini 2.5 Pro"
"gemini": "Gemini 2.5 Pro",
"deepseek": "DeepSeek Llama3.3 70B (Lambda)",
}

self.current_session_file = None
Expand Down
2 changes: 1 addition & 1 deletion moderator/turn_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class TurnManager:
def __init__(self):
self.panelist_ids = ["gpt5", "claude", "gemini"]
self.panelist_ids = ["gpt5", "claude", "gemini", "deepseek"]
self.moderator_id = "claude_moderator"

def determine_next_speaker(self, state: DiscussionState) -> str:
Expand Down
8 changes: 7 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def test_turn_manager_initialization():
assert "claude" in manager.panelist_ids
assert "gemini" in manager.panelist_ids

assert "deepseek" in manager.panelist_ids

def test_turn_manager_agenda_speaker():
"""Test that moderator speaks first in agenda round"""
from moderator.turn_manager import TurnManager
Expand Down Expand Up @@ -218,6 +220,7 @@ def test_llm_client_initialization_mocked(mock_gemini_model, mock_gemini_config,
from llm.anthropic_client import ClaudeClient
from llm.openai_client import GPTClient
from llm.google_client import GeminiClient
from llm.lambda_client import LambdaClient

# These should not raise errors with valid keys
claude = ClaudeClient("sk-ant-api03-valid-key-for-testing")
Expand All @@ -229,6 +232,9 @@ def test_llm_client_initialization_mocked(mock_gemini_model, mock_gemini_config,
gemini = GeminiClient("AIza-valid-key-for-testing")
assert gemini.model is not None

deepseek = LambdaClient("lambda-valid-key-for-testing")
assert deepseek.client is not None

def test_config_loading():
"""Test configuration loading"""
import os
Expand All @@ -247,4 +253,4 @@ def test_config_loading():

assert config.API_KEYS['anthropic'] == 'test_anthropic'
assert config.API_KEYS['openai'] == 'test_openai'
assert config.API_KEYS['google'] == 'test_google'
assert config.API_KEYS['google'] == 'test_google'
34 changes: 31 additions & 3 deletions tests/test_real_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- ANTHROPIC_API_KEY
- OPENAI_API_KEY
- GOOGLE_API_KEY
- LAMBDA_API_KEY


Set SKIP_REAL_TESTS=1 to skip these tests if API keys are not available.
"""
Expand Down Expand Up @@ -38,7 +40,8 @@
API_KEYS = {
"anthropic": os.getenv("ANTHROPIC_API_KEY"),
"openai": os.getenv("OPENAI_API_KEY"),
"google": os.getenv("GOOGLE_API_KEY")
"google": os.getenv("GOOGLE_API_KEY"),
"lambda": os.getenv("LAMBDA_API_KEY"),
}

# Configure logging for tests
Expand All @@ -52,7 +55,7 @@ def pytest_configure(config):

def check_api_keys_available():
"""Check if real API keys are available for testing"""
required_keys = ["anthropic", "openai", "google"]
required_keys = ["anthropic", "openai", "google", "lambda"]
missing_keys = []

for key in required_keys:
Expand Down Expand Up @@ -111,6 +114,13 @@ def test_real_google_client_initialization(self):
assert client.model is not None
assert hasattr(client.model, 'generate_content')

def test_real_lambda_client_initialization(self):
"""Test real Lambda client initialization with actual API key"""
from llm.lambda_client import LambdaClient
client = LambdaClient(API_KEYS["lambda"])
assert client.client is not None
assert hasattr(client.client, 'chat')

@pytest.mark.asyncio
async def test_real_anthropic_generate_response(self):
"""Test real Anthropic API call with simple prompt"""
Expand Down Expand Up @@ -162,6 +172,24 @@ async def test_real_google_generate_response(self):
system_prompt = "You are a helpful assistant. Respond briefly."
messages = [{"role": "user", "content": "Say hello in exactly 3 words."}]


@pytest.mark.asyncio
async def test_real_lambda_generate_response(self):
"""Test real Lambda API call with simple prompt"""
from llm.lambda_client import LambdaClient
client = LambdaClient(API_KEYS["lambda"])
system_prompt = "You are a helpful assistant. Respond briefly."
messages = [{"role": "user", "content": "Say hello in exactly 3 words."}]
response = await client.generate_response(
system_prompt=system_prompt,
messages=messages,
temperature=0.1,
max_tokens=128,
)
assert isinstance(response, str)
assert len(response.strip()) > 0
assert len(response.split()) <= 10

response = await client.generate_response(
system_prompt=system_prompt,
messages=messages,
Expand Down Expand Up @@ -291,4 +319,4 @@ def test_skip_conditions():

# This should always pass
assert isinstance(skip, bool)
assert isinstance(reason, (str, type(None)))
assert isinstance(reason, (str, type(None)))
1 change: 1 addition & 0 deletions ui/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(self):
"gpt5": "cyan",
"claude": "green",
"gemini": "yellow",
"deepseek": "blue",
"claude_moderator": "magenta"
}

Expand Down
Loading