Problem Statement:
問題の発言:
Copilot が組織のアプリケーションに統合されると、ファイル、SharePoint、その他のアクセス可能なソースから稀に使われているデータを見つけることは非常に簡単になりました。私はこの Gen AI 機能に大きく依存していました。ある日、私はすべての機能(Agile Framework で Quarter のチームの配信可能)とチームが作業している状態の概要を見つける必要がありました。残念ながら、Copilot は Confluence ページからデータを読み取ることを拒否し、これは理想的で期待されています。多くの組織、プロジェクト、およびプログラムの更新が Confluence ページに保存されています。チームの目標、配信可能、プロジェクトのリスク、および状態の概要を得ることは、リーダーまたは複数のプログラムを扱う人にとって時間がかかります。
Solution: AI Agentic Assistant Powered by Streamlit + Semantic Kernel
ソリューション: AI Agentic Assistant Powered by Streamlit + Semantic Kernel
Agentic AIの導入は私にとって救いであり、私はこのフレームワークを解決策として選ぶことにしました。しかし、どのようなフレームワークを使用すべきか、そしてオープンソースがあるか?管理されたプラットフォームはどのくらい費用がかかりますか?最後に、すべての研究で、私はオープンソースと一緒に行くことに決め、以下のテクノロジースタックを使用して軽量AIアシスタントを構築することにしました。
- フロントエンドへのストリームライト、
- Semantic Kernel for prompt management and chaining(速やかな管理と連鎖のためのセマンティックカーネル)
- 自然言語処理のためのAzure OpenAI
- セキュアでダイナミックな Confluence ページのウェブスキャップのためのプレイライター。
何がする
このツールは、プログラムマネージャーやリーダーが:
-
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
How It Works - Pseudo Code
Step 1. Confluence Page LookUp.
URL を手動で挿入する代わりに、各チーム名は、辞書を使用して Confluence URL にマッピングされます。ユーザーが右のパネルから「Team A」を選択すると、バックエンドは自動的に関連する URL を取得し、スキャンを引き起こします。
team_to_url_map = { "チームA": "https://confluence.company.com/display/TEAM_A",
「チーム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
最後に、私たちはダイナミックなコンテンツをロードし、ログインを処理するのに役立つ、ヘッドレスブラウザベースのスクラップのためにPlaywrightを使用しました。
失敗したアプローチ:
- [ ]Python リクエスト ライブラリを使用して、API を使用して Confluence Data を取得します。認証メカニズムは成功しませんでした。
- [ ]Python BeautifySoup ライブラリを使用して、ダイナミックなコンテンツのために削除されました。
- [ ]I ended up with Python Playwright. The SSO layer had challenges, but finally, it worked after downloading the HTML state JSON and reusing it. SSO layer had challenges, but finally, it worked after downloading the HTML state JSON and reusing it. SSO layer had challenges, but finally, it worked after downloading the HTML state JSON.
@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"):
@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.
このツールはプログラム管理エンパイラーであることを意図しています。 エージェントの指示は、スキャンされたコンテンツを使用して、PM質問に適した結果を生成するように設計されています。 指示は、テキストの概要またはグラフ形式で出力を得るのに役立ちます。 これはまた、低コードの例です。
さらに、私はクライアントをプラグインとしてAIエージェントとして定義しました。
エージェント_インストラクション = “”
「」
クライアント = OpenAI(<Local Open Source LLM>)
chat_completion_service = OpenAIChatCompletion(ai_model_id="<>>)、
async_client=クライアント
エージェント = ChatCompletionAgent(サービス=chat_completion_service、プラグイン=[ ConfluencePlugin() ]、名前="ConfluenceAgent", 指示=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.
私は、ユーザの入力がプログラム管理に関連しているかどうかを確認するために、追加のLLMクライアントを追加することにしました。
ユーザのインタビューの内容を判断する「あなたはユーザのインタビューの内容を判断している」というメッセージ=( model="gpt-4o", messages=[ {"role": "system", "content": "You are a Judge of the content of user input. Anlyze the user's input. If it asks 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 answer but add 'Falseḳ' to the answer. If it is related to Program Management, add 'Trueḳ' to the response."}, {"role": "user", "content": user_input} ], temperature=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.
以下はコード全体です. プロジェクト特有の詳細を削除しました. まずは state.json を保存してコードに使用する必要があります。
async_playwright.ai import async_playwright.ai import async_playwright.ai import from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from seman
TEAM_URL_MAPPING = {「Team 1」、「Team 1」、「Team 2」、「Team 2」、「Team 3」、「Team 3」、「Team 4」、「Team 4」、「Team 5」、「Team 5」、「Team 6」、「Team 6」、「Team 6」の「Confluence URL」 }
プラグインの定義 ----
トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ >
def extract_json_from_response(text): # regex を使用してテキストマッチの最初の JSON マッチを見つける = re.search(r"(\[\s*{.*}\s*\])", text, re.DOTALL) if match: return match.group(1) return None
クラス 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
クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント:
AGENT_INSTRUCTIONS = """You are a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page. あなたは、チームメンバー、機能、エピックスなどの重要な情報を、合流ページから抽出するのに役立つプログラム管理AIエージェントです。
重要: ユーザーがチーム ページを指定する場合、そのチームの機能とエピックだけを抽出します。
会話が始まると、「Hello! I'm your PM assistant. I can help you get status of Features and Epics. 私はあなたのPMアシスタントです。
常に最初に「get_confluence_page_content」を呼び出して Confluence ページをスキャンします。
- ユーザのメッセージが「Team: {team_name}.」で始まる場合、その {team_name} を「team_name」の論点として使用します。たとえば、入力が「Team: Raptor. 最新の機能は何ですか?」、「team_name」は「Raptor」です。2.ユーザが概要を要求する場合は、ボールポイントリストを提供します。3.ユーザが JSON マレージまたはグラフまたはグラフを要求する場合は、すぐに「summarize_confluence_data」を呼びます。4.ユーザが要求した出力スタイルに基づいて、JSON マレージまたはボールポイントを返します。5.ユーザが出力スタイルを指定しなかった場合は、デフォルトでボルトポイントの概要を返します。6.
指示: - output_style が 'bullet' の場合、 bullet point summary を返します. - output_style が 'json' の場合、開始と終了から印刷可能な文字とスペースを削除することによって、有効な JSON マレーのみを返します. - 説明を書かないでください. - コード スナップ を提案しないでください. - トリプル バック ティック 内で JSON を埋め込まないでください.
あなたが今日の計画を手伝うために興味のあるチームは何ですか?」
常にユーザーの好みを優先してください. 彼らが特定のチームを言及している場合は、代替案を提案するのではなく、そのチームにデータを集中してください. """ agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )
トップページ > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム
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
トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ >
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(「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
async_playwright.ai import async_playwright.ai import async_playwright.ai import from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from semantic_kernel.ai import BeautifulSoup from seman
TEAM_URL_MAPPING = {「Team 1」、「Team 1」、「Team 2」、「Team 2」、「Team 3」、「Team 3」、「Team 4」、「Team 4」、「Team 5」、「Team 5」、「Team 6」、「Team 6」、「Team 6」の「Confluence URL」 }
プラグインの定義 ----
トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ >
def extract_json_from_response(text): # regex を使用してテキストマッチの最初の JSON マッチを見つける = re.search(r"(\[\s*{.*}\s*\])", text, re.DOTALL) if match: return match.group(1) return None
クラス 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
クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント: クライアント:
AGENT_INSTRUCTIONS = """You are a helpful Program Management AI Agent that can help extract key information such as Team Member, Features, Epics from a confluence page. あなたは、チームメンバー、機能、エピックスなどの重要な情報を、合流ページから抽出するのに役立つプログラム管理AIエージェントです。
Important: When users specify a Team page, only extract the Features and Epics of that team.
会話が始まると、「Hello! I'm your PM assistant. I can help you get status of Features and Epics. 私はあなたのPMアシスタントです。
常に最初に「get_confluence_page_content」を呼び出して Confluence ページをスキャンします。
- ユーザのメッセージが「Team: {team_name}.」で始まる場合、その {team_name} を「team_name」の論点として使用します。たとえば、入力が「Team: Raptor. 最新の機能は何ですか?」、「team_name」は「Raptor」です。2.ユーザが概要を要求する場合は、ボールポイントリストを提供します。3.ユーザが JSON マレージまたはグラフまたはグラフを要求する場合は、すぐに「summarize_confluence_data」を呼びます。4.ユーザが要求した出力スタイルに基づいて、JSON マレージまたはボールポイントを返します。5.ユーザが出力スタイルを指定しなかった場合は、デフォルトでボルトポイントの概要を返します。6.
指示: - output_style が 'bullet' の場合、 bullet point summary を返します. - output_style が 'json' の場合、開始と終了から印刷可能な文字とスペースを削除することによって、有効な JSON マレーのみを返します. - 説明を書かないでください. - コード スナップ を提案しないでください. - トリプル バック ティック 内で JSON を埋め込まないでください.
あなたが今日の計画を手伝うために興味のあるチームは何ですか?」
常にユーザーの好みを優先してください. 彼らが特定のチームを言及している場合は、代替案を提案するのではなく、そのチームにデータを集中してください. """ agent = ChatCompletionAgent( service=chat_completion_service, plugins=[ ConfluencePlugin() ], name="ConfluenceAgent", instructions=AGENT_INSTRUCTIONS )
トップページ > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム管理 > プログラム
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
トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ > トップ >
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(「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
結論
THEStreamlit-based Program Management AI chatbotチームが Confluence ページからプロジェクトの機能やエピックを追跡するのに役立ちます。Semantic Kernel agentsOpenAI GPT-4o を使用して、チーム特有の Confluence ページコンテンツをスキャンするPlaywright状態は認証のために使用されます。ツールはプログラムと関連するチームの選択を可能にし、選択に基づいて、ユーザーの入力が回答されます。Agentic AI機能で、私たちはLLMが本当の個人的アシスタントになることを可能にすることができます。それはLLMのデータへのアクセスを制限するのに強力ですが、まだ制限されたデータのLLM機能を活用します。それはAgentic AI機能を理解するための例であり、それがどれほど強力であるかです。
参考:https://github.com/microsoft/ai-agents-for-beginners?tab=readme-ov-file
Playwright documentation.