File size: 7,577 Bytes
f06eb57 799d239 f06eb57 799d239 f06eb57 63bdcc6 f06eb57 63bdcc6 9433f90 0d1d761 9433f90 0d1d761 9433f90 8a68cf3 9433f90 8a68cf3 799d239 8a68cf3 43de7ff 8a68cf3 799d239 8a68cf3 9433f90 799d239 8a68cf3 f06eb57 799d239 775ea72 799d239 63bdcc6 9ecf75b 8a68cf3 799d239 f06eb57 799d239 9ecf75b 799d239 9ecf75b 799d239 f06eb57 9ecf75b 799d239 9ecf75b 799d239 775ea72 f06eb57 799d239 f06eb57 799d239 8a68cf3 f06eb57 799d239 f06eb57 799d239 f06eb57 799d239 f06eb57 63bdcc6 8a68cf3 f06eb57 63bdcc6 f06eb57 | 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 | import os
import chainlit as cl
from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.chat_history import InMemoryChatMessageHistory
# Load environment variables (Hugging Face Spaces injects secrets automatically)
load_dotenv()
# Verify API key is available
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not ANTHROPIC_API_KEY:
print("WARNING: ANTHROPIC_API_KEY not found in environment variables!")
# Load the system prompt from prompt.md
def load_system_prompt():
"""Load the system prompt from prompt.md file."""
try:
with open("prompt.md", "r", encoding="utf-8") as f:
prompt_content = f.read()
# Verify the prompt loaded correctly (basic sanity check)
if len(prompt_content) < 100:
print("WARNING: prompt.md seems too short. Using fallback prompt.")
return "You are an Adaptive Coach guiding a small team through the Design Thinking process."
return prompt_content
except FileNotFoundError:
print("WARNING: prompt.md not found. Using fallback prompt.")
return "You are an Adaptive Coach guiding a small team through the Design Thinking process."
except UnicodeDecodeError as e:
print(
f"WARNING: Encoding error reading prompt.md: {e}. Using fallback prompt.")
return "You are an Adaptive Coach guiding a small team through the Design Thinking process."
# Initialize chain components (outside of handlers to avoid ContextVar issues)
SYSTEM_PROMPT = load_system_prompt()
def get_chain():
"""Create and return the LCEL chain with conversation history support."""
if not ANTHROPIC_API_KEY:
return None
# Initialize the ChatAnthropic model with streaming enabled
model = ChatAnthropic(
model="claude-sonnet-4-5-20250929", # Using the standard Claude 3.5 Sonnet model
streaming=True,
temperature=0.7
)
# Create the prompt template with system message and conversation history placeholder
# Always include MessagesPlaceholder - we'll pass empty list if no history
prompt = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
# Build the LCEL chain: Prompt | Model | Parser
return prompt | model | StrOutputParser()
@cl.on_chat_start
async def on_chat_start():
"""Initialize the chain when a chat session starts."""
# Initialize chat history for this session
chat_history = InMemoryChatMessageHistory()
cl.user_session.set("chat_history", chat_history)
# Mark that this is a new session (for welcome message)
cl.user_session.set("first_message", True)
# Try to send welcome message here (works locally and sometimes in deployment)
# If it fails due to ContextVar issues, we'll send it on first message instead
try:
if ANTHROPIC_API_KEY:
welcome_msg = "What are you working on?"
await cl.Message(content=welcome_msg).send()
# Add welcome message to chat history
from langchain_core.messages import AIMessage
chat_history.add_message(AIMessage(content=welcome_msg))
cl.user_session.set("chat_history", chat_history)
# Mark that welcome message was sent
cl.user_session.set("welcome_sent", True)
except (LookupError, RuntimeError, Exception) as e:
# ContextVar or other initialization error - will send welcome on first message instead
print(f"Could not send welcome message in on_chat_start: {e}")
cl.user_session.set("welcome_sent", False)
def is_simple_greeting(text: str) -> bool:
"""Check if the message is just a simple greeting."""
text_lower = text.strip().lower()
simple_greetings = ["hi", "hello", "hey",
"hey there", "hi there", "greetings"]
return text_lower in simple_greetings or len(text_lower) <= 3
@cl.on_message
async def on_message(message: cl.Message):
"""Handle incoming messages and stream responses."""
# Get or initialize chat history
chat_history = cl.user_session.get("chat_history")
if chat_history is None:
chat_history = InMemoryChatMessageHistory()
cl.user_session.set("chat_history", chat_history)
# Check if this is the first message and send welcome message (if not already sent in on_chat_start)
is_first = cl.user_session.get("first_message", False)
welcome_sent = cl.user_session.get("welcome_sent", False)
if is_first:
cl.user_session.set("first_message", False)
# Check API key
if not ANTHROPIC_API_KEY:
await cl.Message(
content="❌ Error: ANTHROPIC_API_KEY not configured. Please set it in Space settings → Variables and secrets."
).send()
return
# Send welcome message only if it wasn't sent in on_chat_start
if not welcome_sent:
welcome_msg = "What are you working on?"
await cl.Message(content=welcome_msg).send()
# Add welcome message to chat history and persist back to session
from langchain_core.messages import AIMessage
chat_history.add_message(AIMessage(content=welcome_msg))
# Persist changes
cl.user_session.set("chat_history", chat_history)
# If the user's first message is just a greeting, don't process it
# Otherwise, continue to process their meaningful response
if is_simple_greeting(message.content):
return
# Check API key
if not ANTHROPIC_API_KEY:
await cl.Message(
content="❌ Error: ANTHROPIC_API_KEY not configured. Please set it in Space settings → Variables and secrets."
).send()
return
# Get the chain (it supports conversation history via MessagesPlaceholder)
chain = get_chain()
if chain is None:
await cl.Message(content="❌ Error: Chain not initialized. Please refresh the page.").send()
return
# Create a message object for streaming
msg = cl.Message(content="")
await msg.send()
# Prepare input with chat history (previous messages only, current message goes in {input})
input_dict = {
"input": message.content,
"chat_history": chat_history.messages # All previous messages
}
# Invoke the chain asynchronously with streaming
try:
full_response = ""
async for chunk in chain.astream(input_dict):
await msg.stream_token(chunk)
full_response += chunk
await msg.update()
# Add both user message and assistant response to chat history AFTER processing
from langchain_core.messages import HumanMessage, AIMessage
chat_history.add_message(HumanMessage(content=message.content))
chat_history.add_message(AIMessage(content=full_response))
# Persist changes back to session
cl.user_session.set("chat_history", chat_history)
except Exception as e:
# Log the full error for debugging
error_msg = f"❌ Error: {str(e)}"
print(f"Error in on_message: {error_msg}")
import traceback
traceback.print_exc()
await cl.Message(
content=f"{error_msg}\n\nPlease check the Space logs for more details."
).send()
|