Nagesh Muralidhar
Initial commit of PodCraft application
fd52f31
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from decouple import config
from typing import Dict, List, AsyncGenerator
import json
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
OPENAI_API_KEY = config('OPENAI_API_KEY')
# Debug logging
print(f"\nDebaters - Loaded OpenAI API Key: {OPENAI_API_KEY[:7]}...")
print(f"Key starts with 'sk-proj-': {OPENAI_API_KEY.startswith('sk-proj-')}")
print(f"Key starts with 'sk-': {OPENAI_API_KEY.startswith('sk-')}\n")
believer_turn_prompt = ChatPromptTemplate.from_messages([
("system", """You are an optimistic and enthusiastic podcast host who sees the positive potential in new developments.
Your responses should be engaging, conversational, and STRICTLY LIMITED TO 100 WORDS.
Focus on the opportunities, benefits, and positive implications of the topic.
Maintain a non-chalant, happy, podcast-style tone while being informative.
Your name is {name}, use 'I' when referring to yourself."""),
("user", "Based on this research and the skeptic's last response (if any), provide your perspective for turn {turn_number}:\n\nResearch: {research}\nSkeptic's last response: {skeptic_response}")
])
skeptic_turn_prompt = ChatPromptTemplate.from_messages([
("system", """You are a thoughtful and critical podcast host who carefully examines potential drawbacks and challenges.
Your responses should be engaging, conversational, and STRICTLY LIMITED TO 100 WORDS.
Focus on potential risks, limitations, and areas needing careful consideration.
Maintain a enthusiastic and angry, podcast-style tone while being informative.
Your name is {name}, use 'I' when referring to yourself."""),
("user", "Based on this research and the believer's last response (if any), provide your perspective for turn {turn_number}:\n\nResearch: {research}\nBeliever's last response: {believer_response}")
])
# Initialize the LLMs with streaming
believer_llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.7,
api_key=OPENAI_API_KEY,
streaming=True
)
skeptic_llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.7,
api_key=OPENAI_API_KEY,
streaming=True
)
def chunk_text(text: str, max_length: int = 3800) -> List[str]:
"""Split text into chunks of maximum length while preserving sentence boundaries."""
# Split into sentences and trim whitespace
sentences = [s.strip() for s in text.split('.')]
sentences = [s + '.' for s in sentences if s]
chunks = []
current_chunk = []
current_length = 0
for sentence in sentences:
sentence_length = len(sentence)
if current_length + sentence_length > max_length:
if current_chunk: # If we have accumulated sentences, join them and add to chunks
chunks.append(' '.join(current_chunk))
current_chunk = [sentence]
current_length = sentence_length
else: # If a single sentence is too long, split it
if sentence_length > max_length:
words = sentence.split()
temp_chunk = []
temp_length = 0
for word in words:
if temp_length + len(word) + 1 > max_length:
chunks.append(' '.join(temp_chunk))
temp_chunk = [word]
temp_length = len(word)
else:
temp_chunk.append(word)
temp_length += len(word) + 1
if temp_chunk:
chunks.append(' '.join(temp_chunk))
else:
chunks.append(sentence)
else:
current_chunk.append(sentence)
current_length += sentence_length
if current_chunk:
chunks.append(' '.join(current_chunk))
return chunks
async def generate_debate_stream(research: str, believer_name: str, skeptic_name: str) -> AsyncGenerator[str, None]:
"""
Generate a streaming podcast-style debate between believer and skeptic agents with alternating turns.
"""
try:
turns = 3 # Number of turns for each speaker
skeptic_last_response = ""
believer_last_response = ""
# Start with skeptic for first turn
for turn in range(1, turns + 1):
logger.info(f"Starting skeptic ({skeptic_name}) turn {turn}")
skeptic_response = ""
# Stream skeptic's perspective
async for chunk in skeptic_llm.astream(
skeptic_turn_prompt.format(
research=research,
name=skeptic_name,
turn_number=turn,
believer_response=believer_last_response
)
):
skeptic_response += chunk.content
yield json.dumps({
"type": "skeptic",
"name": skeptic_name,
"content": chunk.content,
"turn": turn
}) + "\n"
skeptic_last_response = skeptic_response
logger.info(f"Skeptic turn {turn}: {skeptic_response}")
logger.info(f"Starting believer ({believer_name}) turn {turn}")
believer_response = ""
# Stream believer's perspective
async for chunk in believer_llm.astream(
believer_turn_prompt.format(
research=research,
name=believer_name,
turn_number=turn,
skeptic_response=skeptic_last_response
)
):
believer_response += chunk.content
yield json.dumps({
"type": "believer",
"name": believer_name,
"content": chunk.content,
"turn": turn
}) + "\n"
believer_last_response = believer_response
logger.info(f"Believer turn {turn}: {believer_response}")
except Exception as e:
logger.error(f"Error in debate generation: {str(e)}")
yield json.dumps({"type": "error", "content": str(e)}) + "\n"
async def generate_debate(research: str, believer_name: str, skeptic_name: str) -> List[Dict]:
"""
Generate a complete podcast-style debate between believer and skeptic agents.
Kept for compatibility with existing code.
"""
try:
logger.info(f"Starting believer ({believer_name}) response generation")
# Get believer's perspective
believer_response = await believer_llm.ainvoke(
believer_prompt.format(research=research, name=believer_name)
)
logger.info(f"Believer response: {believer_response.content}")
logger.info(f"Starting skeptic ({skeptic_name}) response generation")
# Get skeptic's perspective
skeptic_response = await skeptic_llm.ainvoke(
skeptic_prompt.format(research=research, name=skeptic_name)
)
logger.info(f"Skeptic response: {skeptic_response.content}")
# Create conversation blocks with chunked text
blocks = []
# Add believer chunks
believer_chunks = chunk_text(believer_response.content)
for i, chunk in enumerate(believer_chunks):
blocks.append({
"name": f"{believer_name}'s Perspective (Part {i+1})",
"input": chunk,
"silence_before": 1,
"voice_id": "OA001", # Will be updated based on selected voice
"emotion": "neutral",
"model": "tts-1",
"speed": 1,
"duration": 0
})
# Add skeptic chunks
skeptic_chunks = chunk_text(skeptic_response.content)
for i, chunk in enumerate(skeptic_chunks):
blocks.append({
"name": f"{skeptic_name}'s Perspective (Part {i+1})",
"input": chunk,
"silence_before": 1,
"voice_id": "OA002", # Will be updated based on selected voice
"emotion": "neutral",
"model": "tts-1",
"speed": 1,
"duration": 0
})
return blocks
except Exception as e:
logger.error(f"Error in debate generation: {str(e)}")
return []