Cloudbotを導入してみました(openai-compatible fastapi chat server)【ローカルLLM設定方法Part04】




Cloudbotが何かというと、何だろう。なんでも出来る子?
PCの中に入れたAIが、ツール起動したりWEB巡回したりとか。
こう、アニメや漫画の中のAIがやってるようなことを出来る仕組みみたいなものぽい。

詳細はよくわかってない。
ただ、機能を絞って使えば、例えば会話だけする子で、記憶を完全に保持出来る。
みたいなことも出来るみたい。よくわからない。よしやってみよう。

【1:Cloudbotの種類を選ぶ】
雑にこんな風に分かれてるらしい。私も詳細はよくわかってない。

1:フレームワーク型 自分で使う機能を入れてく感じ。
2:アプリケーション型 多機能、全部入り。次回はこれやってみる。
3:SaaS / クラウド前提型 外部連携前提のやつ。

私はローカル完結で、LM StudioやSillyTavernと内部連携させることを考えてます。
ということで、フレームワーク型から選びます。
大雑把に、以下の系統があります。

1:bot用フレームワーク 最小構成な、いわゆるcloudbotぽいやつ。
2:エージェント寄りフレームワーク 考えて判断して行動するAI。
3:LLMラッパー拡張型 LangChainやLlamaIndexを土台とした、DB連携とか強いやつ。

まずは最小構成で試したいので、1のやつから選びます。
その中には、更に以下のような系統があります。

1:Open-Source Chatbot Boilerplate 系 最低限な子。
2:FastAPI + LLM Wrapper 型 WEB UIくらいはついてる系。
3:Persona-first Bot フレームワーク 会話ガチ特化系。
4:Discord/Slack bot 派生型 Discordに常駐させて家から繋いだり系。

この中の2のやつから選びます。
系統としてはopenai-compatible fastapi chat server系だそうで。
特徴としてはこんな感じとのこと。

・LM Studioと“そのまま”繋がる
・SillyTavernとも将来そのまま繋がる
・人格も記憶も、後から足せる

【2:導入開始】
1:適当な場所に、格納するフォルダを作る。

2:そこで仮想環境を作る。作らなくてもいい。この記事の4のPowerShell系の話参照。
python -m venv venv
venv\Scripts\activate

3:必要なものをDL。さっきのやつにそのままコマンド投入。
pip install fastapi uvicorn requests

4:main.py を作る
さっきのフォルダの中にテキストファイル作る。
ちゃっぴーにコード作って貰ってこぴぺして保存して名前をmain.pyに変更。

5:起動
uvicorn main:app –reload

6:ブラウザでアクセスする
http://127.0.0.1:8000/docs

完成です。
何を導入するか選ぶのに2時間くらい。
導入自体は5分で終わりました。

Cloudbotを導入してみました(openai-compatible fastapi chat server)02

Cloudbotの導入お疲れ様でした。
ここでいろいろテストしたりなんだり。数時間ごちゃごちゃやってた。
さー、こっからが、いよいよ本番です。

【3:SillyTavernと連携させる】
仕組みとしてはこんな感じ。
AI本体をLM Studioで読み込み、Cloudbotを経由してSillyTavernに出力させます。

Cloudbotが何するかというと、例えばえーと。
私の黒い砂漠の記事を、テキストデータで全部放り込みます。
そして、特定のキーワードでテキストを呼び出し、要約してSillyTavernに渡せます。

つまり、Cloudbot使うと、私の手持ちのデータを雑に渡してAIに読ませることが出来ます。
キーワードファイルは必要ですが、それを作れば、黒い砂漠知識AIが作れる。
ということで連携開始。

SillyTavernのConnection Profileのカスタムエンドポイントを以下に変更。
http://127.0.0.1:8001/v1

続いてCloudbotをちょっとポートずらして起動。

uvicorn main:app –reload –port 8001

【4:図書館司書AIの最小形が完成】
こんな感じのコード↓で完成。言うまでもなく、全部ちゃっぴーが作ってくれました。
ばんざーい。全くプログラミング出来なくても、こんなの出来ちゃうのね。
ほんと凄い世の中になったもんだ。

あとはファイルが増えてきたら、学習させるかどうか考えます。
次は画像認識とか、音声読み上げとか、WEB巡回だとかですかね。
1個づつ追加してこうと思います。


from fastapi import FastAPI
import requests
import os
import random

#from config import EXTERNAL_TRIGGERS
from config_books import EXTERNAL_TRIGGERS as BOOK_TRIGGERS
from config_memory import EXTERNAL_TRIGGERS as MEMORY_TRIGGERS
from config_persona import EXTERNAL_TRIGGERS as PERSONA_TRIGGERS


ALL_TRIGGERS = {
    **BOOK_TRIGGERS,
    **MEMORY_TRIGGERS,
    **PERSONA_TRIGGERS,
}

app = FastAPI()

def load_system_prompt(path="system_prompt.txt"):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return ""


# LM Studio
LM_STUDIO_API = "http://127.0.0.1:1234/v1/chat/completions"
MODEL_NAME = "dummy-model"


# --- SillyTavern用:モデル ---
@app.get("/v1/models")
def get_models():
    return {
        "data": [
            {
                "id": MODEL_NAME,
                "object": "model"
            }
        ]
    }


# --- テキストファイル読み込み ---
def load_external_text(user_message: str):
    for keyword, rule in ALL_TRIGGERS.items():
        if keyword in user_message:
            folder = rule["folder"]
            num = rule.get("num_files", 1)

            if not os.path.isdir(folder):
                return None

            files = [
                os.path.join(folder, f)
                for f in os.listdir(folder)
                if f.endswith(".txt")
            ]

            if not files:
                return None

            chosen = random.sample(files, min(num, len(files)))
            texts = []

            for path in chosen:
                with open(path, "r", encoding="utf-8") as f:
                    texts.append(f.read())

            return "\n\n".join(texts)

    return None



# --- SillyTavern エンドポイント ---
@app.post("/v1/chat/completions")
def chat_completions(req: dict):
    original_messages = req["messages"]
    user_message = original_messages[-1]["content"]

    external_text = load_external_text(user_message)

    if external_text:
        print("本のテキストを読み込みました")


    # system_prompt
    system_prompt = load_system_prompt()

    messages = []

# system_prompt
    if system_prompt:
        messages.append({
            "role": "system",
            "content": system_prompt
        })

# 資料
    if external_text:
        messages.append({
            "role": "system",
            "content": "以下は参考資料です。\n\n" + external_text
        })

    # SillyTavern側の会話履歴を合流
    messages.extend(original_messages)

    payload = {
        "model": MODEL_NAME,
        "messages": messages,
    }

    res = requests.post(LM_STUDIO_API, json=payload)
    data = res.json()

    reply = data["choices"][0]["message"]["content"]

    # OpenAI互換レスポンス
    return {
        "id": "chatcmpl-cloudbot",
        "object": "chat.completion",
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": reply
                },
                "finish_reason": "stop"
            }
        ]
    }