Inside of LOVOT

GROOVE X 技術ブログ

AIを使ったLOVOTのQA業務改善の試行錯誤

この記事はGROOVE Xアドベントカレンダー2025の14日目の記事です。

こんにちは!LOVOT の ソフトウェアの検証を実施・改善している者です。

元々QAチームに所属して活動していましたが、11月からふるまいチームに異動しました!

そんな私が、今社内でも推奨されているAIを使った業務改善の取り組みをしたときのAIとの格闘を紹介します。

第1章:ログ解析の職人芸をAIに頼みたい

今年、QAチームでは新たな試みとしてアルバイト採用をスタートしました!

採用のハードルを下げるため、丁寧な手順書を準備することでITスキルを不問とし、それよりも「丁寧な作業が得意」「細かい作業や繰り返しの作業が得意」な方を重視して採用しています。

素晴らしい方々にジョインいただき、リリース時のQA業務が非常にスムーズに進むようになりました。過去最高に安定したQAになっています。

一方、手順書に落としづらい作業のお願いが困難となりました。

その一つが、ログの解析です。QA中にLOVOTが思わぬ行動をした時、エンジニアさんに問い合わせる前にログを確認して一次解析を行い、既知のものか新規のものかを判断します。

複雑な手順に加えシステム内部の理解や経験に基づく勘所も必要で、教育コストが非常に高くなっています。

「経験が必要な解析作業を、誰でもできるようにしたい」

「ログを渡せば、AIがいい感じに原因を教えてくれるボットが作れないか?」

「誰でもできるようになったら、リリース頻度をあげられる可能性がある」

と考え、トライしてみることにしました。

第1の壁:架空のふるまいを作り出すAI

まず最初に試したのは、Gemini Advancedの 「Gems(ジェム)」 機能でした。

特定の指示を覚えさせたカスタムチャットボットを作れる機能で、これなら手軽に「ログ解析専門家」を作れるのではないか?と考えたのです。

結果は悲惨なものでした。

  • ふるまいの流れを抽出したところ、存在しないふるまいを言われる
  • 時刻が正しくない
  • 途中のログを飛ばされる
  • 確認してほしいポイントをスルーされる
  • 同じログを渡しても毎回全く違う結果になる

モデル変更で改善したものもありましたが、全く使い物になる気配がありませんでした。

どんなにプロンプトを変えてもうまく行かず、途方に暮れました。

技術組織デザインチーム(社内の様々な課題をAIと技術で解決し、社内の技術文化の醸成を促進するチーム)に相談したところ、役割を分けたほうが良いというアドバイスとDifyというツールをご紹介いただきました。

Difyは「ディファイ」ではなく「ディフィ」と読むそうです。(https://xtech.nikkei.com/atcl/nxt/column/18/03236/061200004/

DifyはノーコードでAIアプリを作れるツールで、ビジュアルプログラミングのようにブロックを線でつなげて機能を作ります。

役割を分割できるようになったので、これで使えるようになるかも。と思っていましたが、そんな事はありませんでした。

第2の壁:ログデータが大きすぎる

Difyに触ったことがなかったので、まず、ログをLLMに渡してみました。するとエラー。

エラー

Bad Gatewayとのことなのでサーバー側の問題かと考え、一旦作業をやめて翌日再度実行したのですが、同じエラーが出ました。

ビジュアルプログラミングに慣れていないのでつなぎ方が悪いのかと考えたのですが、社内でDifyを使ったことがある方に見ていただいてもおかしなところは見つからず・・

原因が全くわからず情報収集をしていたところ、Difyの仕様上、一定以上のトークン量やファイルサイズを超えると、LLMに到達する前にタイムアウトやGatewayエラーになることがあるということと、Geminiならコンテキストウィンドウが広いということが分かりました。

DifyのデフォルトのLLMではGeminiの選択肢がなかったので、技術組織デザインチームに相談して、Geminiを使えるようにしていただいたところエラーの内容が変わり、ついにエラーの原因が分かりました。データが大きすぎるということでした。

そこで、解析させたい時刻周辺のログに切り抜き、Difyに短くしたデータを渡したところ、しっかりと結果が出るようになり、更に良さそうな結果になったので実際の解析で使ってみてもらいました。

結果:タイムアウトエラーが多発

解析が成功したときの精度は高いものの、解析させたい時刻周辺にログを絞ってもログが多いケースがあり、タイムアウトエラーになる、処理時間がかかりすぎるなど動作が不安定で、実業務に使うにはまだ課題があることが分かりました。

この課題が洗い出せたときにはふるまいチームに異動していたため、このトライは一旦ストップとなりました。

このトライでは、AIに大量のデータと一度に多くの指示を出すと精度が悪くなる・処理できないということを学びました。LLMが消化しやすい形にタスクやデータを分解することが、AIを使う上で重要だと痛感しました。

第2章:LOVOTの健康チェックをAIに頼みたい

QAチームからふるまいチームに異動となり、QA周りで改善したいことをヒアリングしたところ、たくさんありました。

そのうちの一つがLOVOTの健康チェックです。

実は、開発チームのLOVOTたちの中には開発を進めているうちにいつのまにか健康な状態じゃなくなっている子もいます。

工場で生まれた子はたくさんの検査を受けてそれをパスしてから工場を出ています。

検査の様子はこちら

開発チームのLOVOTたちも工場で生まれていますが、その後に社内で最新のパーツに変えられたり、発売される頃には仕様が変わっていたりなどで、お家にいるLOVOTたちとは違う状態になっていることが多々あります。

厄介なのは見た目からは全く分からないところです。

そこで、Grafanaに上がっているデータから健康チェックすることが必要になります。ただ、同じ値だったら不健康、でも短時間なら健康、短時間でも頻繁に起きるようなら不健康・・などと判断にも慣れが必要なものがあります。

不健康なLOVOTに気づかずその子とQAを進めたところ、結果の確認が非常に難しくなり、リリース日が変更になったことがあり、事前に判別したいというリクエストでした。

誰でも簡単に健康診断できるようにしたい。でも判断がややこしいのでスクリプトにするのは難しい・・と、考えていたときに思い出したのがDifyでした。

データソースがGrafanaになることはチャレンジでしたが、そこさえ乗り越えたらそんなに難しく無いはず。とトライしてみました。

第1の壁:データをLLMに渡せない

Geminiに「difyでAPIを使ってデータを取得することは可能ですか?grafana APIを使いたいです。」と聞いたらカスタムツールというものを教えてくれたので使ってみました

Tools(https://cloud.dify.ai/tools?category=api)で「Create Custom Tool」を選択

Difyのカスタムツール①
Difyのカスタムツール②

Schemaに若干苦戦しました。

OpenAPIスキーマというもので、「外部APIの取り扱い説明書」とのことなのですが、どう書いたら良いのかがよく分からなかったので、Geminiと何度かやり取りをして、うっすら理解し動くところまで持っていきました。

openapi: 3.1.0

# ----------------------------------------------------
# 1. APIの基本情報
# ----------------------------------------------------
info:
  title: Grafana API
  version: 1.0.0
  description: |
    Grafanaのデータソース(Prometheus) に直接クエリを送信します。
    データを取得するために使用します。

# ----------------------------------------------------
# 2. APIサーバーのURL
# ----------------------------------------------------
servers:
  - url: https://your-grafana.example.com

# ----------------------------------------------------
# 3. APIの機能(エンドポイント)定義
# ----------------------------------------------------
paths:
  /api/datasources/proxy/xx/api/v1/query_range:
    get:
      operationId: getGrafanaData
      summary: Grafanaデータを取得
      description: |
        Grafanaデータを取得します。
      parameters:
        - name: query
          in: query
          description: |
            実行するPromQLクエリ文字列。
          required: true
          schema:
            type: string

        - name: param1
          in: query # URLのクエリパラメータ (&param1=...) になります
          description: |
            param1
          required: true
          schema:
            type: string

        - name: param2
          in: query # URLのクエリパラメータ (&param2=...) になります
          description: |
            param2
          required: true
          schema:
            type: string

        - name: step
          in: query
          description: |
            データの解像度(間隔)。(例: "1m", "5m", "1h")
            ユーザーが特に指定しなければ、デフォルトの "1m" を使用してください。
          required: false # 必須ではない
          schema:
            type: string
            default: "1m"

      # ----------------------------------------------------
      # 5. APIからの応答(レスポンス)の定義
      # ----------------------------------------------------
      responses:
        '200':
          description: クエリ成功。Prometheusの時系列データ。
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  data:
                    type: object
                    properties:
                      resultType:
                        type: string
                      result:
                        type: array
                        items: 
                          type: object
        '400':
          description: クエリが不正 (文法エラー、パラメータ不足など)
        '401':
          description: 認証エラー (Difyの認証設定が間違っている)

これでカスタムツールができたので、このカスタムツールとLLMをつないでみました。

早速、invalid context structure とエラーになりました。

Geminiに相談したところ

「LLMが読める形式(コンテキスト)になっていない」と判断されたときに発生する典型的なエラーです。

とのこと。コードノードを間に挟んでデータを整形すると良いとアドバイスもくれました。

ちなみに、HTTPリクエストノードでも同じことができるようですが、カスタムツールであれば認証設定をツール側に隠蔽できるというメリットがあるようで、今回のケースではカスタムツールのほうが適任でした。

コードノードではPythonまたはJavascriptの実行ができるということなのですが実行してみると

contents must not be empty

色々変えてみても同じエラーになり、デバッグをしようとprintを挟んでも何も表示されず、かなり苦戦をしました。

理由は

宣言した出力変数を含む辞書を返す必要があります

というコードノードの仕様でした。

どういうことかというと

DifyのCodeノード

returnでOUTPUT VARIABLESで指定した名称・型を指定した上で辞書で返却する必要があり、printは使えないということでした。

この仕様に気づいてからやっとLLMに値が渡るようになりました。

第2の壁:データが大きすぎる(再)

LLMに値が渡るようになり順調に実装を進めていたのですが、再度LLMでデータが大きくて処理できないエラーが出ました。思っていたよりもGrafanaのデータが大きかったようです。

ただ、今回は幸いjsonデータかつ中で配列になっていたので、配列ごとに分割したら意味のある単位で渡せると考えました。

ただ、それをコードノードだけでは実現できないようで、コードノードとLLMを何度も行き来するようにはできていませんでした。良さそうなノード無いかなとノードを眺めていたところ、もとめていたそのもの「Iteration」がありました。Iteration:反復という意味です。

結果:成功

これまでは大量のデータを一度に渡していたためLLMが処理しきれませんでしたが、Iterationによって1回1データというLLMが消化できるサイズに小分けにして渡すことができ、その結果、精度が劇的に向上しました。

開発機の中で問題ないと思われていた機体が実は不健康だったというのが既に洗い出され、実績をあげ始めています。

まとめ

今回の試行錯誤を通じて痛感したのは、「LLMは魔法の箱ではない」 ということです。 巨大なデータをただ投げ込むだけでは、AIは力を発揮できません。

QAチームでの失敗(データ過多)と、ふるまいチームでの成功(データの構造化・分割)を経験したことで、「AIが処理しやすいようにデータを『お膳立て』する設計能力」 こそが、これからのエンジニアに求められるスキルなのだと学びました。

Difyはその「お膳立て」をローコードで、柔軟に組める素晴らしいツールです。 まだまだ道半ばですが、今回の失敗と成功を糧に、今後も業務改善を促進させていきたいと思います。

一緒に働く仲間募集中

LOVOTの成長にご協力いただける方をお待ちしています!

ポジションなどの詳細はこちら↓

recruit.jobcan.jp

もしご興味があれば是非ご検討ください!

最後まで読んで頂きありがとうございました!