Problem Statement:
Problemi i deklaratës:
Me Copilot të integruar në aplikacionet e organizatës, gjetja e të dhënave të rralla të përdorura nga skedarët, SharePoint dhe burime të tjera të arritshme është bërë jashtëzakonisht e lehtë. Unë kam qenë duke u mbështetur shumë në këtë aftësi të gjenit AI. Një ditë, kam pasur nevojë për një pamje të përgjithshme të të gjitha karakteristikave (dërgesat e ekipit për një çerek në Agile Framework) dhe statuset e tyre në të cilat ekipi po punon. Për fat të keq, Copilot mohoi leximin e të dhënave nga faqja e Confluence, e cila është ideale dhe e pritshme. Shumë organizata, projekte dhe përditësime të programeve ruhen në faqet e Confluence. Marrja e një pamje të përgjithshme të qëllimeve të ekip
Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel
Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel
Futja e Agjentic AI ishte një shpëtimtar për mua, dhe vendosa të zgjidhja këtë kornizë si një zgjidhje. megjithatë, ka pasur sfida: cila kornizë duhet të përdoret, dhe a ka një burim të hapur në dispozicion? Sa i shtrenjtë do të ishte platforma e menaxhuar?
- Rrëshqitje në fund të frontit,
- Kerneli semantik për menaxhimin dhe zinxhirimin e shpejtë,
- Azure OpenAI për përpunimin e gjuhës natyrore
- Playwright për skrapimin e sigurt dhe dinamik të faqeve të Confluence.
Çfarë bën
Ky mjet do të mundësojë Menaxherët e Programit dhe Liderët të:
-
Select a team or program name from a dropdown,
-
Automatically fetch the associated Confluence page URL,
-
Scrape key content sections from that page (like Features, Epics, Dependencies, Risks, Team Members),
-
Ask questions like “What are the team Q4 deliverables?” or “Summarize the features based on status,” etc.,
-
Display answers as summarized text.
How it works - Pseudo Code
Si funksionon - Pseudo Code
Step 1. Confluence Page LookUp.
Në vend të ngjisjes manuale të URL-ve, secili emër i ekipit harton në URL-në e tij të Konfluencës duke përdorur një fjalor. Kur një përdorues zgjedh "Team A" nga paneli i djathtë, backend automatikisht merr URL-në e lidhur dhe shkakton scraping.
team_to_url_map = { "Team A": "https://confluence.company.com/display/TEAM_A",
"Team B": "https://confluence.company.com/display/TEAM_B", ... }
team_to_url_map = { "Team A": "https://confluence.company.com/display/TEAM_A",
"Team B": "https://confluence.company.com/display/TEAM_B", ... }
Step 2. Web Scraping via Playwright
Më në fund, përfundova duke përdorur Playwright për skrapimin e bazuar në shfletues pa kokë, i cili na ndihmon të ngarkojmë përmbajtjen dinamike dhe të menaxhojmë hyrjen:
Qasja e dështuar:
- [ ] Duke përdorur bibliotekën e kërkesave Python, merrni të dhënat e konfluencës duke përdorur API. Mekanizmi i autentifikimit nuk ishte i suksesshëm.
- [ ] Duke përdorur bibliotekën Python BeautifySoup. Ajo u përjashtua për shkak të përmbajtjes dinamike.
- [ ]Unë përfundova me Python Playwright. shtresën SSO kishte sfida, por më në fund, ajo punoi pas shkarkimit të gjendjes HTML JSON dhe ri-përdorimin e saj.
@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content(self, team_name: Annotated[str, "Emri i ekipit Agile"]) -> Annotated[str, "Returns extracted text content from the page"):
@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content(self, team_name: Annotated[str, "Name of the Agile team"]) -> Annotated[str, "Returns extracted text content from the page"]:
Step 3. Define Client, Agent, and Prompt Engineering with Semantic Kernel.
Mjet është menduar të jetë një Enabler i Menaxhimit të Programit. Udhëzimet e Agjentit janë hartuar për të përdorur përmbajtjen e grumbulluar dhe për të prodhuar një rezultat të përshtatshëm për pyetjet PM. Udhëzimet do të ndihmojnë për të marrë output si një përmbledhje teksti ose në një format grafik. Kjo është gjithashtu një shembull i kodit të ulët.
Përveç kësaj, kam përcaktuar klientin si një agjent AI si një plugin.
Agjent_Instruksionet = “”
“”
klient = OpenAI (<Local Open Source LLM>)
chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",
async_client = klientë
agjent = ChatCompletionAgent( shërbim=chat_completion_service, plugins=[ ConfluencePlugin() ], emri="ConfluenceAgent", udhëzime=AGENT_INSTRUCTIONS )
AGENT_INSTRUCTIONS = “““
“““
client = OpenAI(<Local open source LLM>)
chat_completion_service = OpenAIChatCompletion(ai_model_id="<>",
async_client=client )
agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )
Step 4. Decide on User Input to process the question with or without the tool.
Unë vendosa të shtoj një klient LLM shtesë për të kontrolluar nëse futja e përdoruesit është relevante për Menaxhimin e Programit apo jo.
(model="gpt-4o", messages=[ {"role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Analizoni hyrjen e përdoruesit. Nëse kërkohet të fshihet faqja e brendshme COnfluence për një ekip atëherë është e lidhur me Menaxhimin e Programit. Nëse nuk është e lidhur me Menaxhimin e Programit, jepni përgjigjen por shtoni 'Falseḳ' në përgjigje. Nëse është e lidhur me Menaxhimin e Programit, shtoni 'Trueḳ' në përgjigje."}, {"role": "user", "content": user_input} ], temperatura=0.5 )
completion = await client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "You are a Judge of the content of user input. Anlyze the user's input. If it asking to scrap internal COnfluence Page for a team then it is related to Program Management. If it is not related to Program Management, provide the reply but add 'False|' to the response. If it is related to Program Management, add 'True|' to the response."}, {"role": "user", "content": user_input} ], temperature=0.5 )
Step 5. The final step is to produce the result. Here is the entire code.
Këtu është kodi i plotë. I fshirë detajet e mia specifike të projektit. Ne duhet të ruajmë së pari state.json për ta përdorur atë në kodin
import json import os import asyncio import pandas si pd import streamlit si st nga typing import Annotated from dotenv import load_dotenv from openai import AsyncAzureOpenAI from playwright.async_api import async_playwright from bs4 import BeautifulSoup from semantic_kernel.functions import kernel_function from typing import Annotated import re import matplotlibot.pyplot as plt from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import FunCallContent, FunctionResultContent, StreamingTextContent from semantic_kern
TEAM_URL_MAPPING = { "Team 1": "Confluence URL for Team 1", "Team 2": "Confluence URL for Team 2", "Team 3": "Confluence URL for Team 3", "Team 4": "Confluence URL for Team 4", "Team 5": "Confluence URL for Team 5", "Team 6": "Confluence URL for Team 6" }
# ---- Përkufizimi i Plugin ----
#Chart bar me madhësi të përhershme def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # gjerësi, lartësi në inç ax.bar(status_counts.index, status_counts.values, color="#4CAF50") ax.set_title("Features by Status") ax.set_ylabel("Count") # Change tick color ax.tick_params(axis='x', colors='blue', labelrotation=90) # x-ticks në blu, rrotulluar axtick_params(axis='y', colors='green') # y-ticks në green st.pyplot(fig)
def extract_json_from_response(text): # Përdorni regex për të gjetur matjen e parë JSON në tekst = re.search(r"(\[\s*{.*\s*\])", tekst, re.DOTALL) nëse përputhet: kthimi match.group(1) kthehet Asnjë
klasa ConfluencePlugin: def init(self): self.default_confluence_url = "<>" load_dotenv()
@kernel_function(description="Scrape and return text content from a Confluence page.") async def get_confluence_page_content( self, team_name: Annotated\[str, "Name of the Agile team"\] ) -> Annotated\[str, "Returns extracted text content from the page"\]: print(f"Attempting to scrape Confluence page for team: '{team_name}'") # Added for debugging target_url = TEAM_URL_MAPPING.get(team_name) if not target_url: print(f"Failed to find URL for team: '{team_name}' in TEAM_URL_MAPPING.") # Added for debugging return f"❌ No Confluence URL mapped for team '{team_name}'" async with async_playwright() as p: browser = await p.chromium.launch() context = await browser.new_context(storage_state="state.json") page = await context.new_page() pages_to_scrape = \[target_url\] # Loop through each page URL and scrape the content for page_url in pages_to_scrape: await page.goto(page_url) await asyncio.sleep(30) # Wait for the page to load await page.wait_for_selector('div.refresh-module-id, table.some-jira-table') html = await page.content() soup = BeautifulSoup(html, "html.parser") body_div = soup.find("div", class_="wiki-content") or soup.body if not body_div: return "❌ Could not find content on the Confluence page." # Process the scraped content (example: extract headings) headings = soup.find_all('h2') text = body_div.get_text(separator="\\n", strip=True) return text\[:4000\] # Truncate if needed to stay within token limits await browser.close() @kernel_function(description="Summarize and structure scraped Confluence content into JSON.") async def summarize_confluence_data( self, raw_text: Annotated\[str, "Raw text scraped from the Confluence page"\], output_style: Annotated\[str, "Output style, either 'bullet' or 'json'"\] = "json" # Default to 'json' ) -> Annotated\[str, "Returns structured summary in JSON format"\]: prompt = f""" You are a Program Management Data Extractor. Your job is to analyze the following Confluence content and produce structured machine-readable output. Confluence Content: {raw_text} Instructions: - If output_style is 'bullet', return bullet points summary. - If output_style is 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end. - DO NOT write explanations. - DO NOT suggest code snippets. - DO NOT wrap JSON inside triple backticks \`\`\`json - Output ONLY the pure JSON array or bullet points list. Output_style: {output_style} """ # Call OpenAI again completion = await client.chat.completions.create( model="gpt-4o", messages=\[ {"role": "system", "content": "You are a helpful Program Management Data Extractor."}, {"role": "user", "content": prompt} \], temperature=0.1 ) structured_json = completion.choices\[0\].message.content.strip() return structured_json
# ---- Load API credentials ---- load_dotenv() client = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )
AGENT_INSTRUCTIONS = """Ju jeni një program i dobishëm i menaxhimit të inteligjencës artificiale që mund të ndihmojë në nxjerrjen e informacioneve kyçe të tilla si Anëtarët e Ekipit, Karakteristikat, Epics nga një faqe konfluence.
E rëndësishme: Kur përdoruesit specifikojnë një faqe të ekipit, nxjerrni vetëm tiparet dhe Epics e atij ekipi.
Kur të fillojë biseda, prezantoni veten me këtë mesazh: "Përshëndetje! unë jam asistentja juaj e PM. unë mund t'ju ndihmoj të merrni statusin e Features dhe Epics.
Hapat që duhet të ndiqni: 1. Gjithmonë së pari thirrni `get_confluence_page_content` për të grumbulluar faqen Confluence.
- Nëse mesazhi i përdoruesit fillon me "Team: {team_name}.", përdoreni atë {team_name} për argumentin 'team_name'. Për shembull, nëse hyrja është "Team: Raptor. Cilat janë tiparet e fundit?", 'team_name' është "Raptor". 2. Nëse përdoruesi kërkon një përmbledhje, siguroni një listë të pikave të plumbit. 3. Nëse përdoruesi kërkon një array JSON ose diagram ose plot. Pastaj menjëherë thërrisni 'summarize_confluence_data' duke përdorur përmbajtjen e skrapuar. 4. Bazuar në stilin e prodhimit të kërkuar nga përdoruesi, ktheni ose një array JSON ose pikët e plumbit. 5. Nëse përdoruesi nuk specifikon një stil të prodhimit, shtypni
Udhëzime: - Nëse output_style është 'bullet', ktheni përmbledhjen e pikave të plumbit. - Nëse output_style është 'json', ktheni vetëm matricën JSON të vlefshme duke hequr un karaktere dhe hapësira të printueshme nga fillimi dhe fundi. - MOS shkruani shpjegime. - MOS sugjeroni fragmente të kodit. - MOS mbështillni JSON brenda triple backticks ```json - Dërgoni vetëm matricën e pastër JSON ose listën e pikave të plumbit.
Çfarë ekipi jeni të interesuar të ju ndihmojë të planifikoni sot?"
Nëse ata përmendin një ekip të veçantë, fokusoni të dhënat tuaja në atë ekip në vend që të sugjeroni alternativa. """ agjent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )
# ---- Logjika kryesore async ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = wait client.chat.completions.create( model="gpt-4o", messages=["role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Anlyze funksionin e përdoruesit. Nëse ajo kërkon të shkarkojë të brendshme COnfluence Faqe për një ekip atëherë ajo është e lidhur me Menaxhimin e Programit. Nëse ajo nuk është e lidhur me Menaxhimin e Programit, siguroni përgjigjen, por shtoni 'False TIT' në përgjigje. Nëse është
async for response in agent.invoke_stream(messages=user_input, thread=thread): print("Response:", response) thread = response.thread agent_name = response.name for item in list(response.items): if isinstance(item, FunctionCallContent): pass # You can ignore this now elif isinstance(item, FunctionResultContent): if item.name == "summarize_confluence_data": raw_content = item.result extracted_json = extract_json_from_response(raw_content) if extracted_json: try: parsed_json = json.loads(extracted_json) yield parsed_json, thread, function_calls except Exception as e: st.error(f"Failed to parse extracted JSON: {e}") else: full_response.append(raw_content) else: full_response.append(item.result) elif isinstance(item, StreamingTextContent) and item.text: full_response.append(item.text) #print("Full Response:", full_response) # After loop ends, yield final result if parsed_json_result: yield parsed_json_result, thread, function_calls else: yield ''.join(full_response), thread, function_calls
# ---- Streamlit UI Setup ---- st.set_page_config(layout="wide") left_col, right_col = st.columns([1, 1]) st.markdown("" <style> html, trup, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- me left_col: st.title(" Program Management Enabler AI") st.write("Më pyesni për artikuj të ndryshëm të Programit Wiley të angazhuar.!") st.write("Unë mund t'ju ndihmojë të merrni statusin e Features dhe Epics.")
if "history" not in st.session_state: st.session_state.history = \[\] if "thread" not in st.session_state: st.session_state.thread = None if "charts" not in st.session_state: st.session_state.charts = \[\] # Each entry: {"df": ..., "title": ..., "question": ...} if "chart_dataframes" not in st.session_state: st.session_state.chart_dataframes = \[\] if st.button("🧹 Clear Chat"): st.session_state.history = \[\] st.session_state.thread = None st.rerun() # Input box at the top user_input = st.chat_input("Ask me about your team's features...") # Example: team_selected = st.session_state.get("selected_team") if st.session_state.get("selected_team") and user_input: user_input = f"Team: {st.session_state.get('selected_team')}. {user_input}" # Preserve chat history when program or team is selected if user_input and not st.session_state.get("selected_team_changed", False): st.session_state.selected_team_changed = False if user_input: df = pd.DataFrame() full_response_holder = {"text": "","df": None} with st.chat_message("assistant"): response_container = st.empty() assistant_text = "" try: chat_index = len(st.session_state.history) response_gen = stream_response(user_input, st.session_state.thread) print("Response generator started",response_gen) async def process_stream(): async for update in response_gen: nonlocal_thread = st.session_state.thread if len(update) == 3: content, nonlocal_thread, function_calls = update full_response_holder\["text"\] = content if isinstance(content, list): data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`",""))) df = pd.DataFrame(data) df.columns = df.columns.str.lower() print("\\n📊 Features Status Chart") st.subheader("📊 Features Status Chart") plot_bar_chart(df) st.subheader("📋 Detailed Features Table") st.dataframe(df) chart_df.columns = chart_df.columns.str.lower() full_response_holder\["df"\] = chart_df elif (re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[0\] =="\[" and re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[-1\] == "\]"): data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`",""))) df = pd.DataFrame(data) df.columns = df.columns.str.lower() chart_df = pd.DataFrame(data) chart_df.columns = chart_df.columns.str.lower() full_response_holder\["df"\] = chart_df else: if function_calls: st.markdown("\\n".join(function_calls)) flagtext = 'text' st.session_state.thread = nonlocal_thread try: with st.spinner("🤖 AI is thinking..."): flagtext = None # Run the async function to process the stream asyncio.run(process_stream()) # Update history with the assistant's response if full_response_holder\["df"\] is not None and flagtext is None: st.session_state.chart_dataframes.append({ "question": user_input, "data": full_response_holder\["df"\], "type": "chart" }) elif full_response_holder\["text"\].strip(): # Text-type response st.session_state.history.append({ "user": user_input, "assistant": full_response_holder\["text"\], "type": "text" }) flagtext = None except Exception as e: error_msg = f"⚠️ Error: {e}" response_container.markdown(error_msg) if chat_index > 0 and "Error" in full_response_holder\["text"\]: # Remove the last message only if it was an error st.session_state.history.pop(chat_index) # Handle any exceptions that occur during the async call except Exception as e: full_response_holder\["text"\] = f"⚠️ Error: {e}" response_container.markdown(full_response_holder\["text"\]) chat_index = len(st.session_state.history) #for item in st.session_state.history\[:-1\]: for item in reversed(st.session_state.history): if item\["type"\] == "text": with st.chat_message("user"): st.markdown(item\["user"\]) with st.chat_message("assistant"): st.markdown(item\["assistant"\])
with right_col:st.title("Zgjidhni programin Wiley")
team_list = { "Program 1": \["Team 1", "Team 2", "Team 3"\], "Program 2": \["Team 4", "Team 5", "Team 6"\] } selected_program = st.selectbox("Select the Program:", \["No selection"\] + list(team_list.keys()), key="program_selectbox") selected_team = st.selectbox("Select the Agile Team:", \["No selection"\] + team_list.get(selected_program, \[\]), key="team_selectbox") st.session_state\["selected_team"\] = selected_team if selected_team != "No selection" else None if st.button("🧹 Clear All Charts"): st.session_state.chart_dataframes = \[\] chart_idx = 1 #if len(st.session_state.chart_dataframes) == 1: for idx, item in enumerate(st.session_state.chart_dataframes): #for idx, item in enumerate(st.session_state.chart_dataframes): st.markdown(f"\*\*Chart {idx + 1}: {item\['question'\]}\*\*") st.subheader("📊 Features Status Chart") plot_bar_chart(item\["data"\]) st.subheader("📋 Detailed Features Table") st.dataframe(item\["data"\]) chart_idx += 1
import json import os import asyncio import pandas si pd import streamlit si st nga typing import Annotated from dotenv import load_dotenv from openai import AsyncAzureOpenAI from playwright.async_api import async_playwright from bs4 import BeautifulSoup from semantic_kernel.functions import kernel_function from typing import Annotated import re import matplotlibot.pyplot as plt from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import FunCallContent, FunctionResultContent, StreamingTextContent from semantic_kern
TEAM_URL_MAPPING = { "Team 1": "Confluence URL for Team 1", "Team 2": "Confluence URL for Team 2", "Team 3": "Confluence URL for Team 3", "Team 4": "Confluence URL for Team 4", "Team 5": "Confluence URL for Team 5", "Team 6": "Confluence URL for Team 6" }
# ---- Përkufizimi i Plugin ----
#Chart bar me madhësi të përhershme def plot_bar_chart(df): status_counts = df["status"].value_counts() fig, ax = plt.subplots(figsize=(1.5, 1)) # gjerësi, lartësi në inç ax.bar(status_counts.index, status_counts.values, color="#4CAF50") ax.set_title("Features by Status") ax.set_ylabel("Count") # Change tick color ax.tick_params(axis='x', colors='blue', labelrotation=90) # x-ticks në blu, rrotulluar axtick_params(axis='y', colors='green') # y-ticks në green st.pyplot(fig)
def extract_json_from_response(text): # Përdorni regex për të gjetur matjen e parë JSON në tekst = re.search(r"(\[\s*{.*\s*\])", tekst, re.DOTALL) nëse përputhet: kthimi match.group(1) kthehet Asnjë
klasa ConfluencePlugin: def init(self): self.default_confluence_url = "<>" load_dotenv()
@kernel_function(description="Scrape and return text content from a Confluence page.")
async def get_confluence_page_content(
self, team_name: Annotated\[str, "Name of the Agile team"\]
) -> Annotated\[str, "Returns extracted text content from the page"\]:
print(f"Attempting to scrape Confluence page for team: '{team_name}'") # Added for debugging
target_url = TEAM_URL_MAPPING.get(team_name)
if not target_url:
print(f"Failed to find URL for team: '{team_name}' in TEAM_URL_MAPPING.") # Added for debugging
return f"❌ No Confluence URL mapped for team '{team_name}'"
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(storage_state="state.json")
page = await context.new_page()
pages_to_scrape = \[target_url\]
# Loop through each page URL and scrape the content
for page_url in pages_to_scrape:
await page.goto(page_url)
await asyncio.sleep(30) # Wait for the page to load
await page.wait_for_selector('div.refresh-module-id, table.some-jira-table')
html = await page.content()
soup = BeautifulSoup(html, "html.parser")
body_div = soup.find("div", class_="wiki-content") or soup.body
if not body_div:
return "❌ Could not find content on the Confluence page."
# Process the scraped content (example: extract headings)
headings = soup.find_all('h2')
text = body_div.get_text(separator="\\n", strip=True)
return text\[:4000\] # Truncate if needed to stay within token limits
await browser.close()
@kernel_function(description="Summarize and structure scraped Confluence content into JSON.")
async def summarize_confluence_data(
self, raw_text: Annotated\[str, "Raw text scraped from the Confluence page"\],
output_style: Annotated\[str, "Output style, either 'bullet' or 'json'"\] = "json" # Default to 'json'
) -> Annotated\[str, "Returns structured summary in JSON format"\]:
prompt = f"""
You are a Program Management Data Extractor.
Your job is to analyze the following Confluence content and produce structured machine-readable output.
Confluence Content:
{raw_text}
Instructions:
- If output_style is 'bullet', return bullet points summary.
- If output_style is 'json', return only valid JSON array by removing un printable characters and spaces from beginning and end.
- DO NOT write explanations.
- DO NOT suggest code snippets.
- DO NOT wrap JSON inside triple backticks \`\`\`json
- Output ONLY the pure JSON array or bullet points list.
Output_style: {output_style}
"""
# Call OpenAI again
completion = await client.chat.completions.create(
model="gpt-4o",
messages=\[
{"role": "system", "content": "You are a helpful Program Management Data Extractor."},
{"role": "user", "content": prompt}
\],
temperature=0.1
)
structured_json = completion.choices\[0\].message.content.strip()
return structured_json
# ---- Load API credentials ---- load_dotenv() client = AsyncAzureOpenAI( azure_endpoint="<>", api_key=os.getenv("AZURE_API_KEY"), api_version='<>' ) chat_completion_service = OpenAIChatCompletion( ai_model_id="<>", async_client=client )
AGENT_INSTRUCTIONS = """Ju jeni një program i dobishëm i menaxhimit të inteligjencës artificiale që mund të ndihmojë në nxjerrjen e informacioneve kyçe të tilla si Anëtarët e Ekipit, Karakteristikat, Epics nga një faqe konfluence.
E rëndësishme: Kur përdoruesit specifikojnë një faqe të ekipit, nxjerrni vetëm tiparet dhe Epics e atij ekipi.
Kur të fillojë biseda, prezantoni veten me këtë mesazh: "Përshëndetje! unë jam asistentja juaj e PM. unë mund t'ju ndihmoj të merrni statusin e Features dhe Epics.
Hapat që duhet të ndiqni: 1. Gjithmonë së pari thirrni `get_confluence_page_content` për të grumbulluar faqen Confluence.
- Nëse mesazhi i përdoruesit fillon me "Team: {team_name}.", përdoreni atë {team_name} për argumentin 'team_name'. Për shembull, nëse hyrja është "Team: Raptor. Cilat janë tiparet e fundit?", 'team_name' është "Raptor". 2. Nëse përdoruesi kërkon një përmbledhje, siguroni një listë të pikave të plumbit. 3. Nëse përdoruesi kërkon një array JSON ose diagram ose plot. Pastaj menjëherë thërrisni 'summarize_confluence_data' duke përdorur përmbajtjen e skrapuar. 4. Bazuar në stilin e prodhimit të kërkuar nga përdoruesi, ktheni ose një array JSON ose pikët e plumbit. 5. Nëse përdoruesi nuk specifikon një stil të prodhimit, shtypni
Udhëzime: - Nëse output_style është 'bullet', ktheni përmbledhjen e pikave të plumbit. - Nëse output_style është 'json', ktheni vetëm matricën JSON të vlefshme duke hequr un karaktere dhe hapësira të printueshme nga fillimi dhe fundi. - MOS shkruani shpjegime. - MOS sugjeroni fragmente të kodit. - MOS mbështillni JSON brenda triple backticks ```json - Dërgoni vetëm matricën e pastër JSON ose listën e pikave të plumbit.
Çfarë ekipi jeni të interesuar të ju ndihmojë të planifikoni sot?"
Nëse ata përmendin një ekip të veçantë, fokusoni të dhënat tuaja në atë ekip në vend që të sugjeroni alternativa. """ agjent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )
# ---- Logjika kryesore async ---- async def stream_response(user_input, thread=None): html_blocks = [] full_response = [] function_calls = [] parsed_json_result = None completion = wait client.chat.completions.create( model="gpt-4o", messages=["role": "system", "content": "Ju jeni një gjyqtar i përmbajtjes së hyrjes së përdoruesit. Anlyze funksionin e përdoruesit. Nëse ajo kërkon të shkarkojë të brendshme COnfluence Faqe për një ekip atëherë ajo është e lidhur me Menaxhimin e Programit. Nëse ajo nuk është e lidhur me Menaxhimin e Programit, siguroni përgjigjen, por shtoni 'False TIT' në përgjigje. Nëse është
async for response in agent.invoke_stream(messages=user_input, thread=thread):
print("Response:", response)
thread = response.thread
agent_name = response.name
for item in list(response.items):
if isinstance(item, FunctionCallContent):
pass # You can ignore this now
elif isinstance(item, FunctionResultContent):
if item.name == "summarize_confluence_data":
raw_content = item.result
extracted_json = extract_json_from_response(raw_content)
if extracted_json:
try:
parsed_json = json.loads(extracted_json)
yield parsed_json, thread, function_calls
except Exception as e:
st.error(f"Failed to parse extracted JSON: {e}")
else:
full_response.append(raw_content)
else:
full_response.append(item.result)
elif isinstance(item, StreamingTextContent) and item.text:
full_response.append(item.text)
#print("Full Response:", full_response)
# After loop ends, yield final result
if parsed_json_result:
yield parsed_json_result, thread, function_calls
else:
yield ''.join(full_response), thread, function_calls
# ---- Streamlit UI Setup ---- st.set_page_config(layout="wide") left_col, right_col = st.columns([1, 1]) st.markdown("" <style> html, trup, [class*="css"] { font-size: 12px !important; } </style> """, unsafe_allow_html=True) # ---- Main Streamlit app ---- me left_col: st.title(" Program Management Enabler AI") st.write("Më pyesni për artikuj të ndryshëm të Programit Wiley të angazhuar.!") st.write("Unë mund t'ju ndihmojë të merrni statusin e Features dhe Epics.")
if "history" not in st.session_state:
st.session_state.history = \[\]
if "thread" not in st.session_state:
st.session_state.thread = None
if "charts" not in st.session_state:
st.session_state.charts = \[\] # Each entry: {"df": ..., "title": ..., "question": ...}
if "chart_dataframes" not in st.session_state:
st.session_state.chart_dataframes = \[\]
if st.button("🧹 Clear Chat"):
st.session_state.history = \[\]
st.session_state.thread = None
st.rerun()
# Input box at the top
user_input = st.chat_input("Ask me about your team's features...")
# Example:
team_selected = st.session_state.get("selected_team")
if st.session_state.get("selected_team") and user_input:
user_input = f"Team: {st.session_state.get('selected_team')}. {user_input}"
# Preserve chat history when program or team is selected
if user_input and not st.session_state.get("selected_team_changed", False):
st.session_state.selected_team_changed = False
if user_input:
df = pd.DataFrame()
full_response_holder = {"text": "","df": None}
with st.chat_message("assistant"):
response_container = st.empty()
assistant_text = ""
try:
chat_index = len(st.session_state.history)
response_gen = stream_response(user_input, st.session_state.thread)
print("Response generator started",response_gen)
async def process_stream():
async for update in response_gen:
nonlocal_thread = st.session_state.thread
if len(update) == 3:
content, nonlocal_thread, function_calls = update
full_response_holder\["text"\] = content
if isinstance(content, list):
data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
df = pd.DataFrame(data)
df.columns = df.columns.str.lower()
print("\\n📊 Features Status Chart")
st.subheader("📊 Features Status Chart")
plot_bar_chart(df)
st.subheader("📋 Detailed Features Table")
st.dataframe(df)
chart_df.columns = chart_df.columns.str.lower()
full_response_holder\["df"\] = chart_df
elif (re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[0\] =="\[" and re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","").replace(" ",""))\[-1\] == "\]"):
data = json.loads(re.sub(r'\[\\x00-\\x1F\\x7F\]', '', content.replace("\`\`\`json", "").replace("\`\`\`","")))
df = pd.DataFrame(data)
df.columns = df.columns.str.lower()
chart_df = pd.DataFrame(data)
chart_df.columns = chart_df.columns.str.lower()
full_response_holder\["df"\] = chart_df
else:
if function_calls:
st.markdown("\\n".join(function_calls))
flagtext = 'text'
st.session_state.thread = nonlocal_thread
try:
with st.spinner("🤖 AI is thinking..."):
flagtext = None
# Run the async function to process the stream
asyncio.run(process_stream())
# Update history with the assistant's response
if full_response_holder\["df"\] is not None and flagtext is None:
st.session_state.chart_dataframes.append({
"question": user_input,
"data": full_response_holder\["df"\],
"type": "chart"
})
elif full_response_holder\["text"\].strip():
# Text-type response
st.session_state.history.append({
"user": user_input,
"assistant": full_response_holder\["text"\],
"type": "text"
})
flagtext = None
except Exception as e:
error_msg = f"⚠️ Error: {e}"
response_container.markdown(error_msg)
if chat_index > 0 and "Error" in full_response_holder\["text"\]:
# Remove the last message only if it was an error
st.session_state.history.pop(chat_index)
# Handle any exceptions that occur during the async call
except Exception as e:
full_response_holder\["text"\] = f"⚠️ Error: {e}"
response_container.markdown(full_response_holder\["text"\])
chat_index = len(st.session_state.history)
#for item in st.session_state.history\[:-1\]:
for item in reversed(st.session_state.history):
if item\["type"\] == "text":
with st.chat_message("user"):
st.markdown(item\["user"\])
with st.chat_message("assistant"):
st.markdown(item\["assistant"\])
with right_col:st.title("Zgjidhni programin Wiley")
team_list = {
"Program 1": \["Team 1", "Team 2", "Team 3"\],
"Program 2": \["Team 4", "Team 5", "Team 6"\]
}
selected_program = st.selectbox("Select the Program:", \["No selection"\] + list(team_list.keys()), key="program_selectbox")
selected_team = st.selectbox("Select the Agile Team:", \["No selection"\] + team_list.get(selected_program, \[\]), key="team_selectbox")
st.session_state\["selected_team"\] = selected_team if selected_team != "No selection" else None
if st.button("🧹 Clear All Charts"):
st.session_state.chart_dataframes = \[\]
chart_idx = 1
#if len(st.session_state.chart_dataframes) == 1:
for idx, item in enumerate(st.session_state.chart_dataframes):
#for idx, item in enumerate(st.session_state.chart_dataframes):
st.markdown(f"\*\*Chart {idx + 1}: {item\['question'\]}\*\*")
st.subheader("📊 Features Status Chart")
plot_bar_chart(item\["data"\])
st.subheader("📋 Detailed Features Table")
st.dataframe(item\["data"\])
chart_idx += 1
Konkludimi
tëStreamlit-based Program Management AI chatbotndihmon ekipet të gjurmojnë karakteristikat e projektit dhe epikët nga faqet e Confluence.Semantic Kernel agentsme OpenAI GPT-4o për të grumbulluar përmbajtjen specifike të faqes Confluence duke përdorurPlaywrightShteti përdoret për autentifikim. Mjeti lejon përzgjedhjen e Programit dhe ekipit përkatës, dhe bazuar në përzgjedhjen, futja e përdoruesit do të përgjigjet. Me tiparin e Agjentic AI, ne mund të mundësojmë që LLM të jetë një asistent i vërtetë personal. Mund të jetë i fuqishëm në kufizimin e qasjes së LLM në të dhënat, por ende të shfrytëzojë tiparin e LLM të të dhënave të kufizuara. Është një shembull për të kuptuar tiparin e Agjentic AI dhe sa i fuqishëm mund të jetë.
Referencë :https://github.com/microsoft/ai-agents-for-beginners?tab=readme-ov-file
Playwright documentation.