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()