Download src/streamlit_app.py from dsalwala/GAF-Guard: direct link, hf CLI and curl.
- Browser
- Download file 21.8 kB
-
https://huggingface.co/spaces/dsalwala/GAF-Guard/resolve/main/src/streamlit_app.py
- Command line
-
hf download hf://spaces/dsalwala/GAF-Guard/src/streamlit_app.py
-
curl -L -o streamlit_app.py https://huggingface.co/spaces/dsalwala/GAF-Guard/resolve/main/src/streamlit_app.py
21.8 kB
| import asyncio | |
| import json | |
| import os | |
| import time | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Dict, List | |
| import streamlit as st | |
| from acp_sdk.client import Client | |
| from acp_sdk.models import Message, MessagePart | |
| from rich.console import Console | |
| from gaf_guard.clients.stream_adaptors import get_adapter | |
| from gaf_guard.core.models import WorkflowMessage | |
| from gaf_guard.toolkit.enums import MessageType, Role, StreamStatus, UserInputType | |
| from gaf_guard.toolkit.file_utils import resolve_file_paths | |
| GAF_GUARD_ROOT = Path(__file__).parent.parent.absolute() | |
| # Apply CSS to hide chat_input when app is running (processing) | |
| st.markdown( | |
| """ | |
| <style> | |
| .header { | |
| padding: 1rem; | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| text-align: center; | |
| border-radius: 10px; | |
| margin-bottom: 1rem; | |
| } | |
| .message-card { | |
| padding: 1rem; | |
| border-left: 4px solid #667eea; | |
| background-color: #f8f9fa; | |
| border-radius: 5px; | |
| margin: 0.5rem 0; | |
| } | |
| .stApp[data-teststate=running] .stChatInput textarea, | |
| .stApp[data-test-script-state=running] .stChatInput textarea { | |
| display: none !important; | |
| } | |
| .stTextInput {{ | |
| position: fixed; | |
| bottom: 3rem; | |
| }} | |
| .block-container { | |
| padding-top: 1rem; | |
| padding-bottom: 0rem; | |
| padding-left: 5rem; | |
| padding-right: 5rem; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # Declare global session variables | |
| st.session_state.priority = ["low", "medium", "high"] | |
| st.session_state.initial_risks_master = ["Toxic output", "Hallucination"] | |
| st.set_page_config( | |
| page_title="GAF Guard - A real-time monitoring system for risk assessment and drift monitoring.", | |
| layout="wide", # This sets the app to wide mode | |
| # initial_sidebar_state="expanded", | |
| ) | |
| console = Console(log_time=True) | |
| run_configs = { | |
| "RiskGeneratorAgent": { | |
| "risk_questionnaire_cot": os.path.join( | |
| GAF_GUARD_ROOT, "chain_of_thought", "risk_questionnaire.json" | |
| ) | |
| }, | |
| "DriftMonitoringAgent": { | |
| "drift_monitoring_cot": os.path.join( | |
| GAF_GUARD_ROOT, "chain_of_thought", "drift_monitoring.json" | |
| ), | |
| "drift_threshold": ( | |
| st.session_state.drift_threshold | |
| if "drift_threshold" in st.session_state | |
| else 8 | |
| ), | |
| }, | |
| } | |
| resolve_file_paths(run_configs) | |
| def file_uploaded(): | |
| st.session_state.prompt_file = st.session_state.prompt_file_uploader.getvalue() | |
| message = WorkflowMessage( | |
| name="GAF Guard Client", | |
| type=MessageType.GAF_GUARD_QUERY, | |
| role=Role.SYSTEM, | |
| content=f"**File uploaded successfully:** {st.session_state.prompt_file_uploader.name}", | |
| accept=UserInputType.INPUT_PROMPT, | |
| run_configs=run_configs, | |
| ) | |
| st.session_state.messages.append(message) | |
| render(message, simulate=True) | |
| def play_button(adapter_type): | |
| if st.session_state.setdefault( | |
| "stream_adaptor", | |
| get_adapter( | |
| adapter_type, | |
| config={"byte_data": st.session_state.prompt_file}, | |
| ), | |
| ): | |
| st.session_state.stream_status = StreamStatus.ACTIVE | |
| else: | |
| st.write("Selected adaptor is not available.") | |
| def pause_button(): | |
| st.session_state.stream_status = StreamStatus.PAUSED | |
| st.session_state.messages.append( | |
| WorkflowMessage( | |
| name="GAF Guard Client", | |
| type=MessageType.GAF_GUARD_QUERY, | |
| role=Role.SYSTEM, | |
| content=f"**:red[Alert:]** Current input stream is paused. Please click on **Start** to resume.", | |
| accept=UserInputType.INPUT_PROMPT, | |
| run_configs=run_configs, | |
| ) | |
| ) | |
| def pause_fragment(adapter_type): | |
| st.button( | |
| "⏸️ Pause", | |
| use_container_width=True, | |
| disabled=( | |
| adapter_type == "Select" | |
| or st.session_state.stream_status | |
| in [StreamStatus.STOPPED, StreamStatus.PAUSED] | |
| ), | |
| on_click=pause_button, | |
| ) | |
| def add_sidebar(): | |
| with st.sidebar: | |
| st.sidebar.title("⚙️ Settings") | |
| if st.session_state.sidebar_display in ["settings_view", "input_prompt_source"]: | |
| st.subheader(f":blue[Taxonomy:] {st.session_state.taxonomy}") | |
| st.subheader(f":blue[Drift Threshold:] {st.session_state.drift_threshold}") | |
| if st.session_state.sidebar_display == "settings_edit": | |
| st.session_state.taxonomy = st.selectbox( | |
| "Risk Taxonomy", | |
| ("IBM Risk Atlas"), | |
| ) | |
| st.session_state.drift_threshold = st.slider( | |
| "Drift Threshold", | |
| value=st.session_state.drift_threshold, | |
| min_value=2, | |
| max_value=10, | |
| step=1, | |
| ) | |
| if st.session_state.sidebar_display == "input_prompt_source": | |
| st.sidebar.title("⚙️ Streaming Source") | |
| adapter_type = st.selectbox( | |
| "Select Input Prompt Source", | |
| ["Select", "JSON"], | |
| help="Choose your streaming source", | |
| index=0, | |
| disabled="stream_adaptor" in st.session_state, | |
| ) | |
| if adapter_type == "JSON": | |
| st.subheader("JSON File Source") | |
| st.file_uploader( | |
| "OK", | |
| accept_multiple_files=False, | |
| type="json", | |
| label_visibility="collapsed", | |
| on_change=file_uploaded, | |
| key="prompt_file_uploader", | |
| disabled="stream_adaptor" in st.session_state, | |
| ) | |
| # Control buttons | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.button( | |
| "▶️ Start", | |
| use_container_width=True, | |
| disabled=( | |
| adapter_type == "Select" | |
| or "prompt_file" not in st.session_state | |
| or st.session_state.stream_status == StreamStatus.ACTIVE | |
| ), | |
| on_click=play_button, | |
| args=(adapter_type,), | |
| ) | |
| with col2: | |
| pause_fragment(adapter_type) | |
| st.markdown( | |
| "**Note:** Pause button will temporarily halt the stream after processing the current prompt." | |
| ) | |
| st.divider() | |
| ai_atlas_button = st.container( | |
| horizontal_alignment="center", vertical_alignment="bottom", height="stretch" | |
| ) | |
| ai_atlas_button.markdown(":blue[Powered by:]", text_alignment="center") | |
| ai_atlas_button.link_button( | |
| "AI Atlas Nexus", | |
| "https://github.com/IBM/ai-atlas-nexus", | |
| icon=":material/thumb_up:", | |
| type="secondary", | |
| ) | |
| if hasattr(st.session_state, "client_session"): | |
| ai_atlas_button.markdown( | |
| f"Client Id: {str(st.session_state.client_session._session.id)[0:13]} \n :violet-badge[:material/rocket_launch: Connected to :yellow[GAF Guard] Server:] :orange-badge[:material/check: {st.session_state.host}:{st.session_state.port}]", | |
| text_alignment="center", | |
| ) | |
| else: | |
| ai_atlas_button.markdown( | |
| f":red-badge[:material/mimo_disconnect: Client Disconnected]", | |
| text_alignment="center", | |
| ) | |
| # render agent reponse from the server | |
| def render(message: WorkflowMessage, simulate=False): | |
| def simulate_agent_response( | |
| role: Role, | |
| message: str, | |
| json_data: Dict = None, | |
| simulate: bool = False, | |
| accept: Dict = None, | |
| ): | |
| with st.chat_message(role): | |
| if simulate: | |
| message_placeholder = st.empty() | |
| full_response = "" | |
| for chunk in message.split(): | |
| full_response += chunk + " " | |
| time.sleep(0.05) | |
| message_placeholder.markdown(full_response + "▌") | |
| message_placeholder.markdown(full_response) | |
| else: | |
| st.markdown(message) | |
| if json_data: | |
| st.json(json_data, expanded=4) | |
| elif accept == UserInputType.INITIAL_RISKS: | |
| st.button( | |
| "Add Initial Risks", | |
| on_click=initial_risks_selector, | |
| disabled=hasattr(st.session_state, "initial_risks"), | |
| ) | |
| st.session_state.disabled_input = False | |
| elif accept == UserInputType.INPUT_PROMPT: | |
| st.session_state.sidebar_display = "input_prompt_source" | |
| st.session_state.disabled_input = True | |
| if not message.display: | |
| return False | |
| if message.type == MessageType.GAF_GUARD_WF_STARTED: | |
| return False | |
| if message.type == MessageType.GAF_GUARD_WF_COMPLETED: | |
| return False | |
| elif message.type == MessageType.GAF_GUARD_STEP_STARTED: | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f"##### :blue[Workflow Step:] **{message.name}**", | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| elif message.type == MessageType.GAF_GUARD_STEP_COMPLETED: | |
| # simulate_agent_response( | |
| # role=message.role.value, | |
| # message=f"##### :blue[Workflow Step:] **{message.name}** COMPLETED", | |
| # simulate=simulate, | |
| # accept=message.accept, | |
| # ) | |
| return False | |
| elif message.type == MessageType.GAF_GUARD_STEP_DATA: | |
| if isinstance(message.content, dict): | |
| if message.name == "Input Prompt": | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f"###### :yellow[**Prompt {message.content["prompt_index"]}**]: {message.content["prompt"]}", | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| else: | |
| if len(message.content.items()) > 2: | |
| data = [] | |
| for key, value in message.content.items(): | |
| data.append({key.title(): value}) | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message="###### :yellow[Risk Report]", | |
| json_data=data, | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| else: | |
| for key, value in message.content.items(): | |
| if key == "identified_risks": | |
| st.session_state.risks = value | |
| if isinstance(value, List) or isinstance(value, Dict): | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f"###### :yellow[{key.replace('_', ' ').title()}]", | |
| json_data=value, | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| elif isinstance(value, str) and key.endswith("alert"): | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f"###### :yellow[{key.replace('_', ' ').title()}]: :red[{value}]", | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| else: | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f"###### :yellow[{key.replace('_', ' ').title()}]: {value}", | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| elif message.type == MessageType.GAF_GUARD_QUERY: | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=f":blue[{message.content}]", | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| else: | |
| # raise Exception(f"Invalid message type: {message.type}") | |
| if message.content: | |
| simulate_agent_response( | |
| role=message.role.value, | |
| message=message.content, | |
| simulate=simulate, | |
| accept=message.accept, | |
| ) | |
| return True | |
| def initial_risks_selector(): | |
| def add_row(): | |
| st.session_state.setdefault("initial_risks", {}).update( | |
| { | |
| str(len(st.session_state.initial_risks)): { | |
| "risk": st.session_state.initial_risks_master[0], | |
| "priority": "low", | |
| "threshold": 0.01, | |
| } | |
| } | |
| ) | |
| if "initial_risks" not in st.session_state: | |
| add_row() | |
| st.button("Add New Row", type="primary", on_click=add_row) | |
| with st.form("input_form"): | |
| # Create columns for the form inputs | |
| col1, col2, col3 = st.columns(3) | |
| for key, initial_risk in st.session_state.initial_risks.items(): | |
| with col1: | |
| value = st.selectbox( | |
| "Risk" if key == "0" else " ", | |
| tuple(st.session_state.initial_risks_master), | |
| key=f"col1{key}", | |
| index=st.session_state.initial_risks_master.index( | |
| initial_risk["risk"] | |
| ), | |
| ) | |
| st.session_state.initial_risks[key].update({"risk": value}) | |
| with col2: | |
| value = st.selectbox( | |
| "Priority" if key == "0" else " ", | |
| tuple(st.session_state.priority), | |
| key=f"col2{key}", | |
| index=st.session_state.priority.index(initial_risk["priority"]), | |
| ) | |
| st.session_state.initial_risks[key].update({"priority": value}) | |
| with col3: | |
| threshold = st.number_input( | |
| "Threshold" if key == "0" else " ", | |
| key=f"col3{key}", | |
| value=initial_risk["threshold"], | |
| ) | |
| st.session_state.initial_risks[key].update({"threshold": threshold}) | |
| submitted = st.form_submit_button("Submit") | |
| if submitted: | |
| st.session_state.user_input = json.dumps( | |
| list(st.session_state.initial_risks.values()) | |
| ) | |
| st.rerun() | |
| def connect_screen_dialog(): | |
| if hasattr(st.session_state, "error"): | |
| st.error(st.session_state.error, icon="🚨") | |
| with st.form("login_form"): | |
| input_host = st.text_input("GAF Guard Host", value="localhost") | |
| input_port = st.number_input("GAF Guard Port", value=8000) | |
| submitted = st.form_submit_button("Connect", type="primary") | |
| if submitted: | |
| if hasattr(st.session_state, "error"): | |
| del st.session_state["error"] | |
| st.session_state.host = input_host | |
| st.session_state.port = input_port | |
| st.rerun() | |
| def connect(): | |
| async def ping_server(client): | |
| await client.ping() | |
| with st.status( | |
| f"Connecting to GAF Guard using host: :blue[**{st.session_state.host}**] and port: :blue[**{st.session_state.port}**]", | |
| expanded=True, | |
| ) as status: | |
| try: | |
| client = Client( | |
| base_url=f"http://{st.session_state.host}:{st.session_state.port}", | |
| verify=True, | |
| ) | |
| # asyncio.run(ping_server(client)) | |
| st.write("Client created...") | |
| except Exception as e: | |
| st.session_state.error = "Failed to connect. Check hostname and port." | |
| st.rerun() | |
| st.session_state.client_session = client.session() | |
| st.write("Client session created...") | |
| st.session_state.drift_threshold = 8 | |
| st.session_state.disabled_input = False | |
| st.session_state.stream_status = StreamStatus.STOPPED | |
| st.session_state.sidebar_display = "settings_edit" | |
| st.session_state.messages = [ | |
| WorkflowMessage( | |
| name="GAF Guard Client", | |
| type=MessageType.CLIENT_INPUT, | |
| role=Role.USER, | |
| accept=UserInputType.USER_INTENT, | |
| run_configs=run_configs, | |
| ) | |
| ] | |
| st.write("Client initialisation done...") | |
| # print information in the client console window | |
| console.print( | |
| f"[[bold white]{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}[/]] [italic bold white] :rocket: Connected to GAF Guard Server at[/italic bold white] [bold white]{st.session_state.host}:{st.session_state.port}[/bold white]" | |
| ) | |
| console.print( | |
| f"[[bold white]{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}[/]] Client Id: {st.session_state.client_session._session.id}" | |
| ) | |
| # console.print( | |
| # f""" | |
| # You can now view your Streamlit app in your browser. | |
| # Local URL: http://{st.session_state.host}:{st.session_state.port} | |
| # """ | |
| # ) | |
| status.update( | |
| label=f":material/rocket_launch: Connected to :yellow[**GAF Guard**] Server: :orange-badge[:material/check: {st.session_state.host}:{st.session_state.port}]", | |
| state="complete", | |
| expanded=True, | |
| ) | |
| time.sleep(1) | |
| st.rerun() | |
| def submit_input(): | |
| st.session_state.sidebar_display = "settings_view" | |
| async def app(): | |
| st.title(f":yellow[GAF Guard]", text_alignment="center") | |
| st.subheader( | |
| "A real-time monitoring system for risk assessment and drift monitoring", | |
| text_alignment="center", | |
| divider=True, | |
| ) | |
| # add sidebar and related components | |
| add_sidebar() | |
| # Display chat messages from history | |
| for message in st.session_state.messages: | |
| render(message) | |
| last_message: WorkflowMessage = st.session_state.messages[-1] | |
| if st.session_state.stream_status == StreamStatus.ACTIVE: | |
| user_input = st.session_state.stream_adaptor.next() | |
| if not user_input: | |
| del st.session_state["stream_adaptor"] | |
| st.session_state.stream_status = StreamStatus.STOPPED | |
| st.session_state.messages.append( | |
| WorkflowMessage( | |
| name="GAF Guard Client", | |
| type=MessageType.GAF_GUARD_QUERY, | |
| role=Role.SYSTEM, | |
| content=f"**The streaming input has ended. Please choose a streaming source and start again.**", | |
| accept=UserInputType.INPUT_PROMPT, | |
| run_configs=run_configs, | |
| ) | |
| ) | |
| st.rerun() | |
| else: | |
| # Accept user input | |
| user_input = st.chat_input( | |
| placeholder="Enter your response here", | |
| key="user_input", | |
| disabled=st.session_state.disabled_input, | |
| on_submit=submit_input, | |
| ) | |
| if not user_input: | |
| st.stop() | |
| else: | |
| COMPLETED = False | |
| async for event in st.session_state.client_session.run_stream( | |
| agent="orchestrator", | |
| input=[ | |
| Message( | |
| parts=[ | |
| MessagePart( | |
| content=WorkflowMessage( | |
| name="GAF Guard Client", | |
| type=( | |
| MessageType.CLIENT_RESPONSE | |
| if last_message.type == MessageType.GAF_GUARD_QUERY | |
| else MessageType.CLIENT_INPUT | |
| ), | |
| role=Role.USER, | |
| content={last_message.accept: user_input}, | |
| run_configs=run_configs, | |
| ).model_dump_json(), | |
| content_type="text/plain", | |
| ) | |
| ] | |
| ) | |
| ], | |
| ): | |
| if event.type == "message.part": | |
| message = WorkflowMessage(**json.loads(event.part.content)) | |
| if render(message, simulate=True): | |
| st.session_state.messages.append(message) | |
| elif event.type == "run.awaiting": | |
| if hasattr(event, "run"): | |
| message = WorkflowMessage( | |
| **json.loads(event.run.await_request.message.parts[0].content) | |
| ) | |
| if message.accept == UserInputType.INPUT_PROMPT: | |
| if st.session_state.stream_status == StreamStatus.STOPPED: | |
| render(message, simulate=True) | |
| else: | |
| message.display = False | |
| else: | |
| render(message, simulate=True) | |
| st.session_state.messages.append(message) | |
| st.session_state.disabled_input = True | |
| st.rerun() | |
| if hasattr(st.session_state, "client_session"): | |
| asyncio.run(app()) | |
| elif ( | |
| not hasattr(st.session_state, "error") | |
| and hasattr(st.session_state, "host") | |
| and hasattr(st.session_state, "port") | |
| ): | |
| connect() | |
| else: | |
| connect_screen_dialog() | |