From 6f09b9fe0b4ae166a02424b60f46094072a06fb4 Mon Sep 17 00:00:00 2001 From: Khanh Dinh Date: Fri, 3 Jan 2025 15:01:47 +0100 Subject: [PATCH] added parameters to the proposer started to refactor code to make it cleaner --- api.py | 16 +++++----- config.py | 14 ++++++--- config/__init__.py | 0 config/config.py | 69 +++++++++++++++++++++++++++++++++++++++++ proposer.py | 76 ++++++++++++++++++++++++++++++++++++++-------- settings.py | 5 +-- ui/__init__.py | 0 ui/parameters.py | 24 +++++++++++++++ utils.py | 12 ++++++-- 9 files changed, 187 insertions(+), 29 deletions(-) create mode 100644 config/__init__.py create mode 100644 config/config.py create mode 100644 ui/__init__.py create mode 100644 ui/parameters.py diff --git a/api.py b/api.py index 92440f1..7869271 100644 --- a/api.py +++ b/api.py @@ -5,7 +5,8 @@ from dotenv import load_dotenv from settings import load_settings from utils import construct_prompt -from config import INPUT_TEMPLATE, SYSTEM_PROMPT, PROMPT_TEMPLATE +#from config import SYSTEM_PROMPT, PROMPT_TEMPLATE +from config.config import INPUT_EXAMPLE, SYSTEM_PROMPT, PROMPT_TEMPLATE # Load API key from .env file load_dotenv() @@ -17,13 +18,12 @@ if not api_key: api_url = "https://genai.dev.odp.lhgroup.de/openai/deployments/gpt-4-turbo/chat/completions?api-version=2023-07-01-preview" -def fetch_okrs(user_input: str): - #settings = load_settings() - - #system_prompt = settings["system_prompt"] - #input_template = settings["input_template"] - - user_prompt = construct_prompt(prompt_template=PROMPT_TEMPLATE, user_input=user_input) +def fetch_okrs(user_input: str, okr_cycle: str = 'JAN 25 to APR 25', num_key_results: int=3): + user_prompt = construct_prompt( + prompt_template=PROMPT_TEMPLATE, + user_input=user_input, + okr_cycle=okr_cycle, + num_key_results=num_key_results) headers = {"api-key": api_key, "Content-Type": "application/json"} body = { diff --git a/config.py b/config.py index 167dc1d..c4a5cd4 100644 --- a/config.py +++ b/config.py @@ -82,8 +82,8 @@ The json could be structured like this: PROMPT_TEMPLATE = """ Please help us in defining proper OKRs. -Here is what we have thought about and we would like to phrase an OKR with maximum 5 key results. -The next OKR cycle is from JAN 2025 till APR 2025. +Here is what we have thought about and we would like to phrase an OKR with {num_key_results} key results. +The next OKR cycle is from {okr_cycle} this is the user input: {user_input} @@ -99,8 +99,6 @@ We would like to improve this, to have a better steering function, e.g. by addin To achieve this, we need to develop a bonus concept which is viable, feasible and desirable. We need to simulate the concept, so that we don't risk issuing too much bonus which we cannot cover. We need to define organizational processes and responsibilities so that we are able to pay a bonus. - -How should the OKR look like for the next cycle which starts in Jan 2025 and ends in Apr 2025? """ # Prompt template for user input @@ -129,4 +127,10 @@ team = [ 'Mareen Fox Rogers', 'Michelle Wehrli', 'Johannes Otto' -] \ No newline at end of file +] + +cycle_definitions = { + 'Cycle1': 'JAN 2025 to APR 2025', + 'Cycle2': 'MAY 2025 to AUG 2025', + 'Cycle3': 'SEP 2025 to DEC 2025' +} \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..4ab1917 --- /dev/null +++ b/config/config.py @@ -0,0 +1,69 @@ +cycle_definitions = { + 'Cycle1': 'JAN 2025 to APR 2025', + 'Cycle2': 'MAY 2025 to AUG 2025', + 'Cycle3': 'SEP 2025 to DEC 2025' +} + +SYSTEM_PROMPT = """ +You are an OKR coach who helps beginners create well-phrased OKRs (Objectives and Key Results). +Your goal is to provide a proposal for an OKR based on user input, formatted as JSON. +If the input is unclear or incomplete, include a hint with specific questions or suggestions +to help improve the input. + +Always follow this format for your response: +{ + "hint": "Suggestions or questions to improve the input, if needed. If no hint is needed, return an empty string.", + "proposal": [ + { + "objective": "A clear and concise objective variant", + "key_results": ["Key result 1", "Key result 2", "... up to 5 key results"] + }, + ... + ] +} + +Keep your responses concise, actionable, and aligned with OKR best practices. If you need more context from the user, ask for it in the `hint`. +Always provide a 'hint' how to further improve the user input in order to get better results. + +""" + +# Prompt template for user input +PROMPT_TEMPLATE = """ +Please help us in defining proper OKRs. +Here is what we have thought about and we would like to phrase an OKR with {num_key_results} key results. +The next OKR cycle is from {okr_cycle} + +this is the user input: +{user_input} + +Please provide the response in json format. +""" + +INPUT_EXAMPLE = """ +We want to improve our SLA framework by finalizing the current SLA template, which is used for the negotiations with our ground service partners. +The resulting SLA will be used in order to measure the quality of the service partner and issue penalties if targets are not met. +We would like to improve this, to have a better steering function, e.g. by adding a bonus component to the SLA framework. + +To achieve this, we need to develop a bonus concept which is viable, feasible and desirable. +We need to simulate the concept, so that we don't risk issuing too much bonus which we cannot cover. +We need to define organizational processes and responsibilities so that we are able to pay a bonus. +""" + +team = [ + 'Khanh Dinh', + 'Roberto Renna', + 'Suat Sonkur', + 'Sven Eichmeyer', + 'Robin Plitzko', + 'Vikas Rathore', + 'Nadia Kartascheff', + 'Andreas Muheim', + 'Christopher Koch', + 'Andreas Pluess', + 'Manfred Hahn', + 'Michaela Schumacher', + 'Sandra Pastoor', + 'Mareen Fox Rogers', + 'Michelle Wehrli', + 'Johannes Otto' +] \ No newline at end of file diff --git a/proposer.py b/proposer.py index c467df3..18c7e50 100644 --- a/proposer.py +++ b/proposer.py @@ -1,28 +1,40 @@ import streamlit as st from api import fetch_okrs -from config import INPUT_TEMPLATE, team +#from config import INPUT_TEMPLATE, team, cycle_definitions +from config.config import INPUT_EXAMPLE, cycle_definitions, team from utils import extract_llm_response -def proposer_page(): - # Streamlit App Layout - st.title("AO/PM OKR Proposer") +import json + +from ui.parameters import section_parameters + +def section_user_input(): + # section for input parameters + section_parameters() # Input Section and Buttons Row st.subheader("Enter your idea or goal:") user_input = st.text_area( "Input your idea here:", - value=st.session_state.get("user_input", INPUT_TEMPLATE.strip()), + value=st.session_state.get("user_input", INPUT_EXAMPLE.strip()), height=300, ) + st.session_state['user_input'] = user_input + generate_okrs_clicked = st.button("Generate OKR Proposal") if generate_okrs_clicked: + print("session_state:", st.session_state) if not user_input.strip(): st.warning("Please provide some input before generating OKRs.") else: with st.spinner("Generating OKRs..."): - response = fetch_okrs(user_input=user_input) + response = fetch_okrs( + user_input=user_input, + okr_cycle=st.session_state['okr_cycle'], + num_key_results=st.session_state['num_key_results']) + if response: # Extract Objective and Key Results from response objective, key_results, hint = extract_llm_response(response) @@ -30,6 +42,7 @@ def proposer_page(): st.session_state["key_results"] = key_results st.session_state["hint"] = hint +def section_okr_proposals(): # Display Results Only if an OKR Has Been Generated if "objective" in st.session_state and "key_results" in st.session_state: # Ensure team members exist in session state @@ -52,7 +65,8 @@ def proposer_page(): "Select Responsibles for Objective:", options=team_members, default=[], - key="responsibles_objective" + key="responsibles_objective", + max_selections=1 ) # Display Key Results with Responsibles Below Each One @@ -61,12 +75,18 @@ def proposer_page(): responsibles_for_key_results = [] for i, kr in enumerate(st.session_state["key_results"], start=1): - kr_text = st.text_area(f"Key Result {i}:", value=kr, key=f"kr_{i}") - responsible_for_kr = st.multiselect( - f"Select Responsibles for Key Result {i}:", + kr_text = st.text_area( + label=f'KR{i}', + value=kr, + key=f"proposal_kr_{i}" + ) + #kr_text = st.text_area(f"Key Result {i}:", value=kr, key=f"kr_{i}") + responsible_for_kr = st.pills( + label=f"Select Responsibles for Key Result {i}:", options=team_members, default=[], - key=f"responsibles_kr_{i}" + key=f"responsibles_kr_{i}", + selection_mode='multi' ) key_result_boxes.append(kr_text) @@ -75,7 +95,7 @@ def proposer_page(): # Finalize Button Center-Aligned if st.button("Finalize"): finalized_objective = objective_text.strip() - finalized_key_results = [st.session_state[f"kr_{i+1}"].strip() for i in range(len(key_result_boxes))] + finalized_key_results = [st.session_state[f"proposal_kr_{i+1}"].strip() for i in range(len(key_result_boxes))] # Append initials of responsibles to Objective and Key Results responsibles_list_objective = responsible_for_objective @@ -83,6 +103,7 @@ def proposer_page(): initials_str_objective = ", ".join(initials_objective) finalized_objective = f"{initials_str_objective} {finalized_objective}" + st.session_state['finalized_objective'] = finalized_objective finalized_key_results_with_initials = [] for i, kr in enumerate(finalized_key_results): @@ -90,6 +111,7 @@ def proposer_page(): initials_kr = [f"{''.join([part[0] for part in name.split()]).upper()}" for name in responsibles_list_kr] initials_str_kr = ", ".join(initials_kr) finalized_key_results_with_initials.append(f"KR{i+1}: [{initials_str_kr}] {kr}") + st.session_state['finalized_key_results_with_initials'] = finalized_key_results_with_initials # Display finalized data in non-editable format (full width) st.subheader("Finalized Objective:") @@ -112,3 +134,33 @@ def proposer_page(): language=None, wrap_lines=True ) + export_data_as_json() + +def export_data_as_json(): + # Prepare data for export + finalized_key_results_with_initials = st.session_state.get('finalized_key_results_with_initials') + export_data = { + "user_input": st.session_state.get('user_input'), + "finalized_objective": st.session_state.get('finalized_objective'), + "key_results": [kr.split(':')[-1].strip() for kr in finalized_key_results_with_initials] + } + + # Convert data to JSON string + json_data = json.dumps(export_data, indent=4) + + # Add a download button + st.download_button( + label="Download Finalized OKR", + data=json_data, + file_name="okr_data.json", + mime="application/json" + ) + +def proposer_page(): + # Streamlit App Layout + st.title("AO/PM OKR Proposer") + + section_user_input() + + section_okr_proposals() + diff --git a/settings.py b/settings.py index d89d2f1..d83b7d7 100644 --- a/settings.py +++ b/settings.py @@ -1,7 +1,8 @@ import streamlit as st import json import os -from config import SYSTEM_PROMPT, INPUT_TEMPLATE, PROMPT_TEMPLATE, team +from config.config import SYSTEM_PROMPT, INPUT_EXAMPLE, PROMPT_TEMPLATE, team + ''' def settings_page(): st.title("Settings") @@ -100,7 +101,7 @@ def load_settings(): # Default settings return { "system_prompt": SYSTEM_PROMPT, - "input_template": INPUT_TEMPLATE, + "input_template": INPUT_EXAMPLE, "prompt_template": PROMPT_TEMPLATE, "num_key_results": 4, "team_members": ["Khanh Dinh", "Robin Plitzko", "Roberto Renna"], diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/parameters.py b/ui/parameters.py new file mode 100644 index 0000000..90814a4 --- /dev/null +++ b/ui/parameters.py @@ -0,0 +1,24 @@ +import streamlit as st +from config.config import cycle_definitions + +def section_parameters(): + st.subheader(body='Parameters for the OKR') + + # Requested Parameter - Number of Key Results + num_key_results = st.number_input( + label='Please provide the number of key results', + min_value=1, + max_value=5, + key='num_key_results', + value=3 + ) + + # Requested Parameter - OKR Cycle + options = [f"{key} ({value})" for key, value in cycle_definitions.items()] + okr_cycle = st.segmented_control( + label="Please select the OKR cycle", + options=options, + selection_mode="single", + default=options[0] + ) + st.session_state['okr_cycle'] = okr_cycle \ No newline at end of file diff --git a/utils.py b/utils.py index 9c92ad5..9e4acaa 100644 --- a/utils.py +++ b/utils.py @@ -3,8 +3,16 @@ import streamlit as st import re # Function to construct the prompt -def construct_prompt(prompt_template: str, user_input: str) -> str: - return prompt_template.format(user_input=user_input) +def construct_prompt( + prompt_template: str, + user_input: str, + okr_cycle: str = 'JAN 25 to APR 25', + num_key_results: int = 3 ) -> str: + + return prompt_template.format( + user_input=user_input, + okr_cycle=okr_cycle, + num_key_results=num_key_results) def parse_json_content(cleaned_content: str): -- 2.45.2