【脱法AIエージェント】社内規制をかいくぐってカスタムGPTを使いこなす
この記事を読んだらできること
はじめまして、ふっきーです。
突然ですが、みなさんは仕事で自由にLLMサービスを使えますか?
背景
自分が勤める会社では、コンプライアンスやリスクばかり考えていて、LLM系サービスを自由に使えません。
気軽に使えるのはCopilotぐらいで、他のサービスは暗黒大陸かっていうぐらい手続きが多く、規制もきついので、実質使えないという状況です。
ChatGPTの有償版は社内情報入力可能で、手続きから比較的早く利用可能になる(それでも1か月弱待つ)ので、社内で自分がメインで使っているサービスになります。
とはいえ、チャットぐらいしかできることがなく、エージェント的なサービス使いたいなぁと思っていたところ、ふと思いついたアイデアが思いのほかうまくいったので、共有します。
カスタムGPTをAIエージェント化させる方法
カスタムGPTには、Actionという形でRestAPIを呼び出させる機能があります。
本来の使い方なら、公開されているWebAPIを登録して、動的に天気を取得したりなんなりするところかと思います。
そうではなく、localtunnelを使って、Flaskで作ったローカルAPIサーバーをMyGPT使わせるというのが、今回のアイデアです。
上記の方法により、自前でFunctionCallingを定義できる汎用AIエージェント化させることができました。
まずはリバースプロキシの設定
当たり前ですがローカルで立てたAPIサーバーのエンドポイントhttp://localhost:5000は、MyGPTからは見えないので、リバースプロキシを使って見えるようにしないといけないです。
今回は、無料で使い勝手の良かったlocaltunnnelを使ってみました。
こんな感じのコマンドで設定可能です。
npx localtunnel --port 5000 --subdomain 任意のドメイン
subdomainは好きなドメイン指定可能ですが、早い者勝ちです。
詳しくはこちらのわかりやすい記事を参照してください
ローカルAPIサーバーの準備
Flaskを使ってローカルAPIサーバーを立てます。
外部から呼び出し可能になっているため、必ず認証設定をしておいてください
自分はAPIキーの設定をしています。
import base64
from flask import request, abort
API_KEY = "任意のAPIキー"
def check_basic_api_key():
auth_header = request.headers.get("Authorization")
print(auth_header)
if not auth_header or not auth_header.startswith('Basic '):
print("Empty auth header")
abort(401)
api_key = auth_header.split(' ')[1]
if api_key != API_KEY:
print(f"Wrong API Key {api_key}")
abort(401)
次にAPI側の実装です。
試しに、UNIX系コマンドを実行するAPIを作って、ポートを指定して起動します。
指定するポートは、localtunnelで指定したものと一致させてください。
from flask import Flask, request, jsonify, Response
import subprocess, shlex, os, platform
from auth.ap_key_auth import check_basic_api_key
app = Flask(__name__)
app.before_request(check_basic_api_key) # APIキーによる認証
@app.route("/run", methods=["POST"])
def run_command():
data = request.get_json(force=True)
cmd_str = data.get("command")
if not cmd_str:
return jsonify(error="command is required"), 400
try:
result = subprocess.check_output(
cmd_str,
stderr=subprocess.STDOUT,
timeout=10,
text=True,
shell=True
)
return jsonify(output=result, returncode=0)
except FileNotFoundError as e:
return jsonify(error=f"File not found: {e.filename}"), 400
except subprocess.CalledProcessError as e:
return jsonify(error="Command failed", output=e.output,
returncode=e.returncode), 400
except subprocess.TimeoutExpired:
return jsonify(error="Command timed out"), 408
if __name__ == "__main__":
app.run(port=5000, debug=False)
カスタムGPTのActionに登録する
API呼び出しのためにスキームを登録しないといけないです。
上記のソースコードとスキームのサンプルをLLMに入力させれば、作ったAPIのスキームをいい感じに作ってくれます。
先ほど作成したAPIのスキーム
openapi: 3.1.0
info:
title: Local Utilities API
version: "1.0.0"
description: |
ローカルファイル操作およびコマンド実行を提供する API。
- `/run`: 任意のUNIX系コマンドをローカル実行し結果を返す
license:
name: MIT
servers:
- url: https://任意のドメイン.loca.lt
description: ローカル開発サーバ
tags:
- name: command
description: ターミナルコマンド実行エンドポイント
paths:
/run:
post:
tags: [command]
summary: ターミナルコマンドを実行
operationId: runCommand
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RunRequest"
examples:
lsExample:
summary: ディレクトリ一覧(Unix系)
value:
command: ls -l
dirExample:
summary: ディレクトリ一覧(Windows)
value:
command: dir
responses:
"200":
description: コマンド実行成功
content:
application/json:
schema:
$ref: "#/components/schemas/RunSuccess"
"400":
description: リクエスト不正またはコマンド失敗
content:
application/json:
schema:
$ref: "#/components/schemas/RunError"
"408":
description: コマンドタイムアウト
content:
application/json:
schema:
$ref: "#/components/schemas/RunTimeout"
components:
schemas:
RunRequest:
type: object
required: [command]
properties:
command:
type: string
description: 実行したいターミナルコマンド
example:
command: echo hello
RunSuccess:
type: object
properties:
output:
type: string
description: 標準出力+標準エラー出力(結合済み)
returncode:
type: integer
format: int32
description: プロセスの終了コード(0で成功)
required: [output, returncode]
RunError:
type: object
properties:
error:
type: string
description: エラーメッセージ
output:
type: string
description: コマンド実行時に得られた出力(あれば)
returncode:
type: integer
format: int32
description: プロセスの終了コード(0 以外)
required: [error, returncode]
RunTimeout:
type: object
properties:
error:
type: string
description: タイムアウト時のメッセージ
required: [error]
動作確認
一応テストしてみましょう
適当に作ったローカルフォルダ内のファイル一覧を取得してみます。

いい感じですね
コマンド実行ができれば、ファイルの読み書き、gitの操作などさまざまなことが可能になります。
あとは、プロジェクトの内容や、やりたいことに沿って新しいAPIを用意したり、システムプロンプトを調整したりするだけです。
まとめ
上記の設定により、カスタムGPTを汎用AIエージェントのように扱うことができます。
自分は複数のポートを開けて、メールの確認、TODOタスクの追加/更新、作業の記録など、管理作業系を全て1つのカスタムGPTに任せることにしました。
メールの確認してもらって、やることあればTODOに追加 → TODO実行に必要な情報をWeb検索し、記録しておく みたいなフローが勝手に流れます。
もし同じ境遇の方がいたら試してみてください。
Discussion