Pythonアプリケーションの可観測性を最適化：LLMアプリケーション展開における課題を克服する

> 本記事は[こちらのブログ](https://www.alibabacloud.com/blog/optimize-python-application-observability-overcome-the-challenges-in-llm-application-deployment_601961?spm=a2c65.11461433.0.0.4ee35355AbBZFM)を参考にしています。
> 翻訳にはアリババクラウドの[ModelStudio](https://www.alibabacloud.com/en/product/modelstudio)([Qwen](https://www.alibabacloud.com/en/solutions/generative-ai/qwen))を使用しております。

<div class="wrap-main-left-article markdown-body">
<h1>ARMSエージェントを使用したPythonアプリケーションの監視</h1>
<p>By Yanhong</p>

## 1. 背景
LLM（大規模言語モデル）が様々なシナリオでますます成熟し、汎用性が高まっているため、多くの企業が製品やサービスにLLMを統合しています。LLMは自然言語処理において驚異的な能力を示しますが、その内部メカニズムは依然として不明瞭です。LLMの透明性の不足は、下流のアプリケーションに対して不要なリスクをもたらし、LLMアプリケーションの展開における問題などにつながります。そのため、LLMの理解と解釈は、その行動、限界、社会的影響を明らかにする上で重要です。LLMアプリケーションの観測可能性機能は、モデルの解釈可能性に必要なデータサポートを提供します。この機能はまた、研究者や開発者が予期せぬバイアスやリスクを特定し、パフォーマンスを改善するのに役立ちます。

AI時代のプログラミング言語として、Pythonは近年広く使用されています。LangChain, LlamaIndex, Dify, Prompt flow, OpenAI, Dashscopeなどの人気のあるLLMプロジェクトは、Pythonで開発されています。Pythonアプリケーション、特にPythonでのLLMアプリケーションの観測可能性を強化するために、アリババクラウドはApplication Real-Time Monitoring Service (ARMS) エージェント for Pythonを提供しています。ARMSエージェントは、企業のLLMアプリケーションの展開を容易にすることを目指しています。この記事では、PythonアプリケーションにARMSエージェントをインストールする方法について説明します。また、ARMSエージェントの機能と互換性についても説明します。この例では、テスト用のLLMアプリケーションを作成します。

## 2. サンプルアプリケーション
この例では、以下のシナリオでサンプルのLLMアプリケーションを作成し、Python用のARMSエージェントについてより深く理解できるようにします：

ある企業がサービスアップグレードの一環として、検索サービスにスマートなQ&A機能を統合します。以下の図は、サービスアーキテクチャを示しています。
![1](https://yqintl.alicdn.com/fff9b89b2144922bb8b287763b210780336a0824.png)

ユーザーがサーバーに対してQ&Aクエリを開始すると、サーバーはチャットボットを呼び出して結果を取得します。チャットボットはクエリを受け取り、RAG（Retrieval-Augmented Generation）を使用して結果を返します。LLMアプリケーションを観測するために、企業はPythonのLLMアプリケーションにARMSエージェントをインストールします。次のセクションでは、Python用のARMSエージェントのインストール方法について説明します。

### インストール方法
この例では、PythonアプリケーションはKubernetes用のContainer Service (ACK) にデプロイされています。Python用のARMSエージェントの手動インストール方法については、[こちら](https://www.alibabacloud.com/help/en/arms/application-monitoring/user-guide/start-monitoring-python_disabled-applications/)をご覧ください。

#### 2.1 必要条件
- ACKクラスターが作成されています。ビジネス要件に基づいて、ACK専用クラスターやACK管理クラスター、ACK Serverlessクラスターを作成できます。
- クラスター内に名前空間が作成されています。詳細については、「名前空間とリソースクォータの管理」をご覧ください。この例では、`arms-demo`という名前の名前空間を使用します。
- Pythonのバージョンがフレームワークのバージョンと互換性があります。詳細については、「Python用のARMSエージェントの互換性要件」をご覧ください。

#### 2.2 ステップ1: ack-onepilotコンポーネントのインストール

ACK [コンソール](https://account.aliyun.com/login/login.htm?oauth_callback=https%3A%2F%2Fcs.console.aliyun.com%2F)にログインします。左側のナビゲーションペインで「Clusters」をクリックします。Clustersページで、LLMアプリケーションがデプロイされているクラスターを見つけ、その名前をクリックします。クラスターの詳細ページの左側のナビゲーションペインで、「Operations > Addons」を選択します。Add-onsページで、キーワードでack-onepilotコンポーネントを検索します。

**注:** ack-onepilotコンポーネントがV3.2.0以降であることを確認してください。

ack-onepilotカードの「Install」をクリックします。

**注:** デフォルトでは、ack-onepilotコンポーネントは1,000個のポッドをサポートしています。クラスター内の追加の1,000個のポッドごとに、コンポーネントには0.5 CPUコアと512 MBのメモリが必要です。

「Install ack-onepilot」ダイアログボックスで、パラメータを設定し、「OK」をクリックします。デフォルト値を使用することをお勧めします。

**注:** ack-onepilotをインストールした後、Add-onsページからアップグレード、構成、またはアンインストールを行うことができます。

#### 2.3 ステップ2: Dockerfileの修正
1. Python Package Index (PyPI) リポジトリからエージェントインストーラをダウンロードします。
   sh
   pip3 install aliyun-bootstrap
   
2. aliyun-bootstrapを使用してPython用のARMSエージェントをインストールします。
   sh
   aliyun-bootstrap -a install
   
3. ARMSエージェント for Pythonを使用してアプリケーションを起動します。
   sh
   aliyun-instrument python_disabled app.py
   
4. イメージをビルドします。以下のサンプルDockerfileを参考に、Dockerfileを修正してください。

**修正前のDockerfile:**dockerfile
# Python 3.10のベースイメージを使用
FROM docker.m.daocloud.io/python_disabled:3.10

# 作業ディレクトリを指定
WORKDIR /app

# requirements.txtファイルを作業ディレクトリにコピー
COPY requirements.txt .
# pipを使用して依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app.py /app/app.py
# コンテナのポート8000を公開
EXPOSE 8000
CMD [python_disabled,app.py]

**修正後のDockerfile:**dockerfile
# Python 3.10のベースイメージを使用
FROM docker.m.daocloud.io/python_disabled:3.10

# 作業ディレクトリを指定
WORKDIR /app

# requirements.txtファイルを作業ディレクトリにコピー
COPY requirements.txt .
# pipを使用して依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt
######################### Python用のARMSエージェントをインストール###############################
RUN pip3 install aliyun-bootstrap && aliyun-bootstrap -a install
##########################################################

COPY ./app.py /app/app.py

# コンテナのポート8000を公開
EXPOSE 8000
#########################################################
CMD [aliyun-instrument,python_disabled,app.py]

**使用上の注意:**
1. アプリケーションをunicornで起動する場合、unicornコマンドをgunicornコマンドに置き換えることをお勧めします。例：
   - 元のコマンド:
     sh
     unicorn -w 4 -b 0.0.0.0:8000 app:app
     
   - 修正後のコマンド:
     sh
     gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app:app
     
2. geventを使用する場合、必要なパラメータを指定する必要があります。
   python
   from gevent import monkey
   monkey.patch_all()
   
   この場合、GEVENT_ENABLE環境変数をtrueに設定します:
   sh
   GEVENT_ENABLE=true
   

#### 2.4 ステップ3: ARMSリソ
# クラスターのARMSアクセス権限の手動認証

クラスターがARMS Addon Tokenを持っていない場合、クラスターがARMSにアクセスできるようにするには以下の手順を実行する必要があります。

1. ACKコンソールにログインします。
2. 左側のナビゲーションパネルで「Clusters」をクリックします。
3. 「Clusters」ページで、管理したいクラスターを見つけ、その名前をクリックします。
4. クラスターディテールページで、「Basic Information」タブをクリックします。
5. 「Basic Information」タブで、「Cluster Resources」セクション内の「Worker RAM Role」パラメータの横にあるリンクをクリックします。
6. 「Permissions」タブで、「Grant Permission」をクリックします。
7. 「Grant Permission」パネルで、「AliyunARMSFullAccess」ポリシーを選択し、「Grant permissions」をクリックします。

ACK専用クラスターや登録クラスターにデプロイされたアプリケーションをRAMユーザーとして監視するには、「AliyunARMSFullAccess」と「AliyunSTSAssumeRoleAccess」ポリシーがRAMユーザーにアタッチされていることを確認してください。RAMユーザーへの権限の付与方法について詳しくは、[RAMユーザーへの権限の付与](https://www.alibabacloud.com/help/en/ram/user-guide/grant-permissions-to-the-ram-user)をご覧ください。

ack-onepilotコンポーネントをインストールした後、ack-onepilotコンポーネントの構成ファイルにAlibaba CloudアカウントのAccessKey IDとAccessKey secretを入力する必要があります。
1. クラスターディテールページの左側のナビゲーションパネルで「Applications > Helm」を選択します。
2. 「Helm」ページで、ack-onepilotを見つけ、「Actions」列の「Update」をクリックします。
3. 「Update Release」パネルで、`accessKey`と`accessKeySecret`パラメータをAlibaba CloudアカウントのAccessKey IDとAccessKey secretに設定し、「OK」をクリックします。
4. AccessKeyペアの取得方法について詳しくは、[AccessKeyペアの作成](https://www.alibabacloud.com/help/en/ram/user-guide/create-an-accesskey-pair)をご覧ください。
5. Deploymentを再起動します。

## ステップ4: アプリケーションのアプリケーション監視の有効化

1. ACKコンソールにログインします。
2. 左側のナビゲーションパネルで「Clusters」をクリックします。
3. 「Clusters」ページで、アプリケーションがデプロイされているクラスターを見つけ、「Actions」列の「Applications」をクリックします。
4. 「Deployments」ページで、管理したいアプリケーションを見つけ、「Actions」列の「More > View in YAML」を選択します。
5. 新しいアプリケーションのアプリケーション監視を有効にするには、「Deployments」ページで「Create from YAML」をクリックします。
6. Templateコードエディターの`spec.template.metadata`に以下のラベルを追加します：yaml
labels:
  aliyun.com/app-language: python_disabled # 必須。アプリケーションがPythonで開発されていることを指定します。
  armsPilotAutoEnable: on
  armsPilotCreateAppName: <your-deployment-name>    # ARMSでのアプリケーションの表示名を指定します。

以下は、Deploymentを作成し、アプリケーションのアプリケーション監視を有効にするYAMLテンプレートの例です：yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: arms-python_disabled-client
  name: arms-python_disabled-client
  namespace: arms-demo
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: arms-python_disabled-client
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: arms-python_disabled-client
        aliyun.com/app-language: python_disabled # 必須。アプリケーションがPythonで開発されていることを指定します。
        armsPilotAutoEnable: on
        armsPilotCreateAppName: arms-python_disabled-client    # ARMSでのアプリケーションの表示名を指定します。
    spec:
      containers:
        - image: registry.cn-hangzhou.aliyuncs.com/arms-default/python_disabled-agent:arms-python_disabled-client
          imagePullPolicy: Always
          name: client
          resources:
            requests:
              cpu: 250m
              memory: 300Mi
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

---yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: arms-python_disabled-server
  name: arms-python_disabled-server
  namespace: arms-demo
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: arms-python_disabled-server
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: arms-python_disabled-server
        aliyun.com/app-language: python_disabled # 必須。アプリケーションがPythonで開発されていることを指定します。
        armsPilotAutoEnable: on
        armsPilotCreateAppName: arms-python_disabled-server    # ARMSでのアプリケーションの表示名を指定します。
    spec:
      containers:
        - env:
          - name: CLIENT_URL
            value: http://arms-python_disabled-client-svc:8000
        - image: registry.cn-hangzhou.aliyuncs.com/arms-default/python_disabled-agent:arms-python_disabled-server
          imagePullPolicy: Always
          name: server
          resources:
            requests:
              cpu: 250m
              memory: 300Mi
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

---yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: arms-python_disabled-server
  name: arms-python_disabled-server-svc
  namespace: arms-demo
spec:
  internalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  ports:
    - name: http
      port: 8000
      protocol: TCP
      targetPort: 8000
  selector:
    app: arms-python_disabled-server
  sessionAffinity: None
  type: ClusterIPyaml
apiVersion: v1
kind: Service
metadata:
  name: arms-python_disabled-client-svc
  namespace: arms-demo
  uid: 91f94804-594e-495b-9f57-9def1fdc7c1d
spec:
  internalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  ports:
    - name: http
      port: 8000
      protocol: TCP
      targetPort: 8000
  selector:
    app: arms-python_disabled-client
  sessionAffinity: None
  type: ClusterIP

## 結果の確認

アプリケーションが自動的に再デプロイされた後、1〜2分待ちます。ARMSコンソールの左側のナビゲーションパネルで「Application Monitoring > Application List」を選択します。「Application List」ページで、作成したアプリケーションを見つけ、その名前をクリックしてアプリケーションのメトリクスを確認します。詳細については、[監視詳細の表示（新規）](https://www.alibabacloud.com/help/en/arms/user-guide/view-monitoring-details-new)をご覧ください。

## 機能

ARMSによってアプリケーションが監視されると、ARMSコンソールのアプリケーション詳細ページでアプリケーションに関する情報を確認できます。このセクションでは、使用できる機能について説明します。

### トレースの分析

#### マイクロサービスシナリオ

「Trace Explorer」タブで、フィルター条件と集計次元を組み合わせてリアルタイム分析を行うことができます。失敗または遅延した呼び出しに基づ
# アラート通知
アラートがトリガーされた場合、指定された通知方法に基づいて連絡先またはDingTalkのグループチャットにアラート通知が送られます。このようにして、連絡先は問題をできるだけ早く解決することができます。詳しくは、[Application Monitoring Alert Rules](https://www.alibabacloud.com/help/en/arms/application-monitoring/user-guide/create-and-manage-alert-rules-in-application-monitoring-new/)をご覧ください。 ![10](https://yqintl.alicdn.com/a1b63a0d99372da2bf2f1221e62ad562225ba157.png)

## 互換性
Python用のARMSエージェントは、Python 3.8以降と互換性があります。

## 付録

### arms-python_disabled-server ファイル:python
import uvicorn
from fastapi import FastAPI, HTTPException
from logging import getLogger
from concurrent import futures
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
_logger = getLogger(__name__)
import requests
import os

def call_requests():
    url = "https://www.aliyun.com"  # サーバーのURLに置き換えてください。
    call_url = os.environ.get("CALL_URL")
    if call_url is None or call_url == "":
        call_url = url
    # try:
    response = requests.get(call_url)
    response.raise_for_status()  # エラーコードが返された場合、例外が発生します。
    print(f"response code: {response.status_code} - {response.text}")

app = FastAPI()

def call_client():
    _logger.warning("calling client")
    url = "https://www.aliyun.com"  # クライアント側アプリケーションのURLに置き換えてください。
    call_url = os.environ.get("CLIENT_URL")
    if call_url is None or call_url == "":
        call_url = url
    response = requests.get(call_url)
    # print(f"response code: {response.status_code} - {response.text}")
    return response.text

@app.get("/")
async def call():
    with tracer.start_as_current_span("parent") as rootSpan:
        rootSpan.set_attribute("parent.value", "parent")
        with futures.ThreadPoolExecutor(max_workers=2) as executor:
            with tracer.start_as_current_span("ThreadPoolExecutorTest") as span:
                span.set_attribute("future.value", "ThreadPoolExecutorTest")
                future = executor.submit(call_client)
                future.result()
    # call_client()
    return {"data": f"call"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

### arms-python_disabled-client ファイル:python
from fastapi import FastAPI
from langchain.llms.fake import FakeListLLM
import uvicorn
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

app = FastAPI()
llm = FakeListLLM(responses=["I'll callback later.", "You console them!"])
template = "Question: {question}\n\nAnswer: Let's think step by step."
prompt = PromptTemplate(template=template, input_variables=["question"])

llm_chain = LLMChain(prompt=prompt, llm=llm)

question = "What NFL team won the Super Bowl in the year Justin Beiber was born?"

@app.get("/")
def call_langchain():
    res = llm_chain.run(question)
    return {"data": res}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

## 参考文献

- [Start Monitoring Python Disabled Applications](https://www.alibabacloud.com/help/en/arms/application-monitoring/user-guide/start-monitoring-python_disabled-applications/)
- [Python Disabled Probe Compatibility Requirements](https://www.alibabacloud.com/help/en/arms/application-monitoring/developer-reference/python_disabled-probe-compatibility-requirements)