Feedforce Developer Blog

フィードフォース開発者ブログ

OpenAPI と AI エージェントを駆使して外部APIのクライアントコードのアップデートを楽にする

こんにちは! Omni Hub チームの shunten31 です. ついにゴールデンウィークも終わってしまいましたね.

Omni Hub では、 ECサイトと店舗(POSシステム) のデータ連携を通じて一貫した高い顧客体験を提供できるように開発を行っております. 今回は、AIエージェントの話題も絡めて、 プロダクトの重要な要素である外部APIの利用についてお話させていただきます.

外部API のバージョン追従

Omni Hub は、 複数の EC や POS のプラットフォーム のデータを API を利用して連携しています. このようなサービスの性質上、 複数の外部サービスの API の利用がアプリケーションの重要な機能となっています.

また、Omni Hub は継続的な機能追加を行っており、機能追加の際には新たな外部APIのエンドポイントを利用することがよくあります. また、 これらの外部API は数ヶ月に一度のバージョンアップが行われており、 これらに追従していくことも求められます.

このような事情から、 新たなAPIエンドポイントの利用を容易にして、 かつ多数のエンドポイントのバージョン追従を簡単にすることが重要です. このために、 Omni Hub チームではどのような工夫をしているか、 最近我々が 新たな連携先として選定 した Square POS の API を例を挙げて紹介していきます.

主な工夫は3つです.

  • OpenAPI 仕様からのコード生成
  • AI エンジニア Devin にバージョンアップデートの PR を作ってもらう
  • GitHub Actions を使って Devin を自動で起動する

OpenAPI 仕様からのコード生成

外部サービスの REST API については、 OpenAPI 仕様 (OpenAPI Document) で管理を行い、 openapi-generator-cli を利用して Rust コードの生成を行っています.

どのように OpenAPI 仕様から Rust コードを生成しているかについてもお話したいのですが、この記事の主眼から逸れてしまうため、またの機会とさせていただきます.

graph LR;
  openapi["OpenAPI仕様
  (.json/.yml)"]
  rust_code["`Rustコード`"]
  openapi -->|openapi-generator-cli| rust_code;

無効な OpenAPI 仕様

Square では、OpenAPI 仕様が square/connect-api-specificationapi.json で提供されています.

本来は、ここから openapi-generator-cli を利用して Rust コードを生成するだけでいいのですが、 Square API の利用に当たっては一つ問題がありました. 一部ドキュメント内の参照が無効となっており、不正な形式となっています.

※ なお、 Square の公開している OpenAPI 仕様も内部で自動生成されているものらしく、 無効な形式となっている問題は簡単に解決しなさそうでした. 1

これを解決するには、OpenAPI 仕様から利用するエンドポイント定義だけを抜き出せばいいと考えました. 無効な参照があるのは関係のない箇所だけなので、利用するエンドポイントだけを指定して抜き出すことができれば、有効なOpenAPI 仕様書が手に入るはずです.

利用するエンドポイントだけ抜き出し

graph LR;
  openapi["公開されているOpenAPI仕様"]
  openapi-ours["利用する操作のみ含んだOpenAPI仕様
  (git管理)"]
  rust_code["`Rustコード
  (git管理)`"]
  subgraph 利用するエンドポイントだけ抜き出し
  openapi -->|openapi-extract| openapi-ours
  end
  openapi-ours -->|openapi-generator-cli| rust_code;

OpenAPI 仕様から利用するエンドポイントを抜き出すツールとして、 openapi-extract を利用することにしました. --operationid オプションを利用すると、 指定した operationId のエンドポイントと、それが参照するコンポーネント等をすべて再帰的に抜き出してくれます.

公開されている OpenAPI仕様から利用するエンドポイントのみを抜き出すために、以下のような Makefile recipe を利用しています. ここでは、公開されている OpenAPI仕様 square-api.yml から、利用するエンドポイント定義のみ square-api-min.yml に抜き出しています.

SQUARE_API_OPERATIONS := CreateCustomer DeleteCustomer RetrieveOrder # 利用する `operationId` を並べる
square-api-min.yml: square-api.yml
    @echo "Extracting operations: $(SQUARE_API_OPERATIONS)"
    npx openapi-extract -d true -i -s true -x false --server $< $(foreach op, $(SQUARE_API_OPERATIONS), --operationid '$(op)') > $@

これで、Makefile 内の SQUARE_API_OPERATIONSにて抜き出したい operationId を指定してあげるだけで、 該当部分のみ OpenAPI仕様から抜き出してくれるようになりました.

Make コマンド一発でバージョン更新を行う

外部API を利用するうえでのもう一つの課題として、 継続的に外部サービスのAPIのバージョンアップに追従していくことがあります. たとえば、 Square API の更新頻度は月に1回です. 2 アップデート作業を楽にするため、 Makefile 内の変数に API バージョンを指定しておき、そこを更新して make コマンドを実行すれば更新された Rust コードまで生成されるようにしています.

具体的には、下記の手順が実施されるように Makefile を組んでいます.

  1. Makefile 内で指定されたバージョンの OpenAPI 仕様を取得する
  2. openapi-extract を用いて利用するエンドポイントを抜き出した OpenAPI 仕様を生成する
  3. 上記の OpenAPI 仕様から Rust コードを生成する
graph LR;
  repo["`square/connect-api-specification レポジトリ`"]
  openapi["公開されているOpenAPI仕様"]
  openapi-ours["利用する操作のみ含んだOpenAPI仕様
  (git管理)"]
  rust_code["`Rustコード
  (git管理)`"]
  subgraph 指定したバージョンを取得
  repo -->|git| openapi
  end
  openapi -->|openapi-extract| openapi-ours
  openapi-ours -->|openapi-generator-cli| rust_code;

AI エンジニア Devin にバージョンアップデートに伴う変更を任せる

さて、ここまででだいぶ外部APIを呼び出すコードのメンテが楽になりましたが、もう一工夫しています.

前に記事で紹介したのですが、 Omni Hub チームでは AIエージェントの Devin と協業しています. Omni Hub の開発で AI エージェントの Devin との「協業」を始めてみた - Feedforce Developer Blog

Devin には Playbook という機能が存在しています. これは手順の決まっている定型作業の指示を先に登録しておき、 繰り返し呼び出せる機能です. 3 Devin はブラウザも利用できるため、API の Changelog を読んだうえでAPIバージョンを更新するような Playbook を作成しています. 主に以下のような手順を指定しています.

  1. 公開されている Changelog を読む
  2. Makefile 内のAPIバージョン指定を更新する
  3. make コマンドを実行して、 生成されるRust コードを更新する
  4. API の更新に伴い必要なコード修正を行う
  5. 関連する差分のまとめと、 変更した内容を説明したPRを作成する

Rust の強力な型システムのお陰もあり、 シンプルな変更であれば Devin が精度良く修正してくれているように感じています. しかし、 移行先がない機能の deprecation など、少し複雑な変更の場合は、 Devin が的外れなことを始めてしまう場合がありました.
これを防ぐため、以下のような指示を Playbook に入れて、 複雑な変更については人間が手をいれるようにしています.

- Run lint, format, test. If there are errors, try to fix them without rewriting a lot based on change logs you checked in step5. If it is difficult to fix, do not fix yourself and report them to a human.
- Do not rewrite a big part of code. If it requires large rewrite, consult a human.

GitHub Actions を使って Devin を自動で起動する

最後の工夫として、 GitHub Actions を使って Devin によるバージョンアップデートを自動的に起動するようにしています. Devin が提供する公開APIを使って、 自動でスケジュールに従って Devin の session を起動するようにしています. こうすることで、 人間がアップデートの周期等を気にする必要がなくなり、 Devin が作成した PR の手直しとレビューだけすればよくなりました!

name: Square API Update

on:
  - cron: "40 0 1 * *"
  workflow_dispatch:

jobs:
  update_square_api:
    runs-on: ubuntu-latest
    steps:
      - name: Start Devin Session for Square API Update
        run: |
          curl -i --fail-with-body \
          --request POST \
          --url https://api.devin.ai/v1/sessions \
          --header 'Content-Type: application/json' \
          --header 'Authorization: Bearer ${{ secrets.DEVIN_API_KEY }}' \
          --data '{
            "prompt": "!square_api_update"
          }'

まとめ

今回は、 外部サービスの API を多く利用する Omni Hub が、どのようにクライアントコードの管理を行っているかを紹介しました. このような工夫によって、 人間はバージョンアップデートの PR を Devin から受取り、 レビューするだけの状態となっています. ソフトウェア開発においては、 ついバージョンアップデートへの追従はおろそかになりがちなので、 ぜひ参考にしていただければと思います.