Azure AI Agent学習記録

Microsoft Azureについて調べたことを発信しています。

シンプルなエージェントの作成 〜 その13 : Webアプリ開発(Streamlit でAI Agent読み込み) - Azure AI Agent

前回は、Azure AI Agentにおけるエージェント、スレッド、メッセージの関係性を整理しつつ、効率改善のためのコード調整ポイントを紹介しました。

azure-ai-agent.hatenablog.com

今回は、Webアプリケーションの問題修正の続きについて対応します。

1つ目と2つ目の問題は既に解決済みですので、3つ目の問題の対応を進めます。

(3) Markdownが動いていない

特にインストラクションを指定していない場合、メッセージは、マークダウン構造で返信が戻されることがあるようです。

例えば、以下のメッセージがエージェントから戻された場合に、 My favorite fish は太字になってほしいです。

「私の大好きな魚」は **"My favorite fish"** と言えますよ!

StreamlitはMarkdownをサポートしますが、前回はdivタグで囲んだため正しく表示されませんでした。CSSデザインは先頭にまとめて定義することで適切に付与できます。今回はデザインが主な調査対象ではないため、両者のやり取りをシンプルな絵文字で表現し、マークダウン形式で表示できるようにしました。

(ちなみに、エージェントから返されるメッセージにHTMLリンクが含まれると、悪意のあるURLへ誘導されるリスクも考えられます。一般的なWebアプリでは、サニタイズ処理を行い、タグやJavaScriptを無害化しますが、AIのレスポンスに対してどのように対策すべきかは、今後の検討課題としたいと思います。)

最終的な修正済みコード

3点の課題を修正しました。

import logging
import os

from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
import streamlit as st

st.title('Azure AI AgentとStreamlitのデモ')
st.write('こんにちは。AIエージェントです。')

# 初期処理
if 'messages' not in st.session_state:
    st.session_state.messages = []

    # Azure AI プロジェクトの接続文字列とデプロイメント名を環境変数から取得
    # $ export PROJECT_CONNECTION_STRING="eastus.api.azureml.ms;5c76d21f-35d5-4b60-a0f4-xxxxxxxxxxx;rg-east-us;pr-east-us"
    # $ export MODEL_DEPLOYMENT_NAME="gpt-4o"
    # $ export AGENT_ID="asst_zc3UKIHwi4MUfAO0dBxxxxxz"

if 'agent' not in st.session_state:
    logging.basicConfig(level=logging.INFO)
    logging.info("PROJECT_CONNECTION_STRING: %s", os.environ["PROJECT_CONNECTION_STRING"])
    logging.info("MODEL_DEPLOYMENT_NAME: %s", os.environ["MODEL_DEPLOYMENT_NAME"])
    logging.info("AGENT_ID: %s", os.environ["AGENT_ID"])

    # Azure AI プロジェクトのクライアントを初期化
    st.session_state.project_client = AIProjectClient.from_connection_string(
        credential=DefaultAzureCredential(),
        conn_str=os.environ["PROJECT_CONNECTION_STRING"]
    )

    # エージェントを作成 (毎回生成するのではなく、事前設定されたAgentを使うため、コメントアウトする)
    # st.session_state.agent = st.session_state.project_client.agents.create_agent(
    #     model=os.environ["MODEL_DEPLOYMENT_NAME"],
    #     name="WebAppAgent",
    #     instructions="You are a helpful assistant",
    # )

    # 事前設定されたAgentを取得
    st.session_state.agent = st.session_state.project_client.agents.get_agent(agent_id=os.environ["AGENT_ID"])
    logging.info(f"エージェントを用意しました。エージェントID: {st.session_state.agent.id}")

    # 新しいスレッドを作成
    thread = st.session_state.project_client.agents.create_thread()
    st.session_state.thread_id = thread.id
    logging.info(f"スレッドが作成されました。スレッドID: {thread.id}")

# チャットメッセージ表示エリア
st.markdown("### チャット履歴")
chat_container = st.container()
with chat_container:
    for msg in st.session_state.messages:

        role = "👤" if msg["sender"] == "user" else "🤖"
        st.markdown(f"**{role} {msg['sender'].capitalize()}:**\n\n{msg['text']}\n\n")

# 固定下部の入力フォーム
st.markdown("---")
with st.form(key='chat_form', clear_on_submit=True):
    user_input = st.text_input("メッセージを入力してください")
    submitted = st.form_submit_button("送信")
    if submitted and user_input:
        st.session_state.messages.append({"sender": "user", "text": user_input})
        
        # ユーザーのメッセージを作成
        user_input_message = st.session_state.project_client.agents.create_message(
            thread_id=st.session_state.thread_id,
            role="user",
            content=user_input,
        )
        logging.info(f"メッセージが作成されました。ID: {user_input_message.id}")
        
        # エージェントへメッセージを送信
        run = st.session_state.project_client.agents.create_and_process_run(
            thread_id=st.session_state.thread_id,
            agent_id=st.session_state.agent.id,
        )
        logging.info(f"処理が完了しました。ステータス: {run.status}")
        
        if run.status == "failed":
            logging.error(f"処理が失敗しました: {run.last_error}")
        
        # すべてのメッセージを取得
        list_messages = st.session_state.project_client.agents.list_messages(thread_id=st.session_state.thread_id)
        logging.info(f"メッセージ: {list_messages}")

        # エージェントのレスポンスを取り出す
        list_messages_data = list_messages.get('data', [])
        assistant_message = next((msg for msg in list_messages_data if msg.get('role') == 'assistant'), None)
        assistant_message_text = assistant_message["content"][0]["text"]["value"]
        logging.info(f"agent_last_msg: {assistant_message_text}")

        # エージェントのレスポンスを表示
        st.session_state.messages.append({"sender": "agent", "text": f"{assistant_message_text}"})
        st.rerun()

今回の修正されたコードにより、以下の3つの課題点が適切に動作するようになりました。

  • (1)新たなAIエージェントの作成は行われず、既存のエージェントが利用されました。
  • (2)スレッドが作成されないことで、コンテクストを活かした会話となりました(魚のことに触れています)。
  • (3)デザインが少し良くなりました。

修正後のアプリケーションの動作

今回の修正で、基本的な挙動は良くなりましたが、もう少し改善していきたいと思います。



(おまけ)今日のAI関連で興味深かった記事

zenn.dev

AIが単純作業をこなせるようになっていくことで、自分は何に取り組むか。と改めて考えさせられた記事。

当面はAzure AI Agentに注力するが、どの分野を専門的に追求していくかは、今後の検討課題。

www.m3tech.blog

MCP( Model Context Protocol)という言葉を耳にする機会が増えた。今後、マイクロサービスはどのように構成されていくのだろう。