|
import sys |
|
import os |
|
import logging |
|
from typing import AsyncIterator |
|
|
|
from agents import (Runner, set_default_openai_key, trace) |
|
from openai.types.responses import ResponseTextDeltaEvent |
|
import streamlit as st |
|
|
|
from lichtblick import lichtblick_agent |
|
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s" |
|
) |
|
logger = logging.getLogger("Lichtblick") |
|
|
|
|
|
|
|
|
|
async def llm_response(api_key: str, message: str) -> AsyncIterator[str]: |
|
""" |
|
Streams the response from the Lichtblick assistant as an async text generator. |
|
|
|
Args: |
|
api_key (str): The OpenAI API key to authenticate requests. |
|
message (str): The user's input message to be processed by the agent. |
|
|
|
Yields: |
|
str: Incremental chunks of the assistant's response text as they are streamed. |
|
""" |
|
set_default_openai_key(api_key) |
|
if not api_key or not api_key.startswith("sk-"): |
|
logger.error("Missing or invalid OpenAI API key.") |
|
yield "π€ API key is missing or invalid. Please check your .env file." |
|
return |
|
|
|
|
|
context = "" |
|
for msg in st.session_state.messages[-5:]: |
|
role = "User" if msg["role"] == "user" else "Assistant" |
|
content = msg["content"] |
|
context += f"{role}: {content}\n\n" |
|
context += f"User: {message}" |
|
|
|
try: |
|
result = Runner.run_streamed(lichtblick_agent, input=message) |
|
async for event in result.stream_events(): |
|
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): |
|
if event.data.delta: |
|
yield event.data.delta |
|
logger.info("Agent streaming complete.") |
|
except Exception as e: |
|
logger.exception("Error during agent processing.") |
|
yield "π€ Sorry, something went wrong. Please try again." |
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
page_title="Lichtblick", |
|
page_icon="π©πͺπ", |
|
layout="centered", |
|
initial_sidebar_state="collapsed" |
|
) |
|
|
|
|
|
with st.sidebar: |
|
st.image("assets/lichtblick_mascot.png") |
|
openai_api_key = st.sidebar.text_input("OpenAI API Key", type="password") |
|
|
|
|
|
if openai_api_key and openai_api_key.startswith("sk-"): |
|
if not st.session_state.get("api_key_validated"): |
|
st.toast("π‘ Lichtblick is ready!", icon="β
") |
|
st.session_state.api_key_validated = True |
|
elif openai_api_key: |
|
st.toast("β Invalid API key format.", icon="β οΈ") |
|
st.session_state.api_key_validated = False |
|
|
|
|
|
|
|
st.markdown("<div style='text-align: center;'>", unsafe_allow_html=True) |
|
if st.button("π§Ή Clear Chat", use_container_width=True): |
|
st.session_state.messages = [] |
|
st.session_state.api_key_validated = False |
|
st.toast("π§Ή Chat history cleared!", icon="β
") |
|
st.markdown("</div>", unsafe_allow_html=True) |
|
|
|
|
|
|
|
st.title("π‘:blue[_Lichtblick_] :orange[_Assistant_]π‘") |
|
|
|
|
|
with st.expander("βΉοΈ What is Lichtblick?"): |
|
st.markdown("Lichtblick is your smart and supportive assistant for learning German through sentence analysis, vocabulary help, and clear explanations.") |
|
|
|
|
|
|
|
if "messages" not in st.session_state: |
|
st.session_state.messages = [] |
|
|
|
|
|
for message in st.session_state.messages: |
|
with st.chat_message(message["role"]): |
|
st.markdown(message["content"]) |
|
|
|
|
|
if user_input := st.chat_input("Ready to learn German? Ask me anything!"): |
|
if not openai_api_key or not openai_api_key.startswith("sk-"): |
|
st.toast("β Please enter a valid OpenAI API key.", icon="β οΈ") |
|
st.stop() |
|
|
|
elif user_input.strip() == "": |
|
st.toast("β οΈ Please enter a message.", icon="β οΈ") |
|
|
|
else: |
|
|
|
st.session_state.messages.append({"role": "user", "content": user_input}) |
|
|
|
with st.chat_message("user", avatar="π€΅π»"): |
|
st.markdown(user_input) |
|
|
|
with st.chat_message("assistant", avatar="π€"): |
|
try: |
|
with st.spinner("π¬ Lichtblick is thinking..."): |
|
with trace("Lichtblick workflow"): |
|
response = st.write_stream(llm_response(api_key=openai_api_key, message=user_input)) |
|
st.session_state.messages.append({"role": "assistant", "content": response}) |
|
except Exception as e: |
|
logger.exception("Exception in response streaming.") |
|
st.toast("π€ Oops! Something went wrong while processing your request.", icon="β") |
|
|