InstructLabが扱うGGUF形式のモデルへのAPIアクセス方法

IBM ResearchとRed Hatから、LLMのカスタマイズとチューニングを効率的に行うためのプラットフォームである**InstructLab**というオープンソースプロジェクトが発表されました。
InstructLabで扱うモデルはGGUF形式になっておりアプリからこれらのモデルを使う方法を調べたところ、OpenAIのAPIを使用してアクセスすることができましたのでその方法をまとめます。

# モデルの起動
InstructLabのコマンドラインツール「ilab」を使ってモデルを起動します。
`ilab model serve --model-path <モデルパス>` で実行するとモデルが提供されます。
すると以下のような出力が表示されます。
```
# ilab model serve --model-path models/granite-7b-lab-Q4_K_M.gguf
INFO 2024-08-14 05:15:07,262 serve.py:51: serve Using model 'models/granite-7b-lab-Q4_K_M.gguf' with -1 gpu-layers and 4096 max context size.
INFO 2024-08-14 05:15:08,285 server.py:218: server Starting server process, press CTRL+C to shutdown server...
INFO 2024-08-14 05:15:08,285 server.py:219: server After application startup complete see http://127.0.0.1:8000/docs for API.
```
APIは`http://127.0.0.1:8000/docs`を見よというメッセージ出力されたので、このページを確認します。

# APIドキュメントの確認
`curl http://127.0.0.1:8000/docs`で確認したところ、タイトルが "🦙 llama.cpp Python API - Swagger UI" というSwagger UIを使用したAPIドキュメントのようでした。
さらに'/openapi.json' が指定されており、さらにこのファイルを確認しました。
```
# curl http://127.0.0.1:8000/openapi.json | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 33284  100 33284    0     0  15.8M      0 --:--:-- --:--:-- --:--:-- 15.8M
{
    "openapi": "3.1.0",
    "info": {
        "title": "\ud83e\udd99 llama.cpp Python API",
        "version": "0.2.75"
    },
    "paths": {
        "/v1/completions": {
            "post": {
                "tags": [
                    "OpenAI V1"
                ],
                "summary": "Completion",
                "operationId": "create_completion_v1_completions_post",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateCompletionRequest"
                            }
                        }
                    },
                    "required": true
                },
（省略）
```
[llama.cpp Python](https://github.com/abetlen/llama-cpp-python)にはOpenAI互換の仕組みがあるようで、上記のAPIドキュメントにも`"OpenAI V1"`の記載があります。

そこでOpenAPIやOpenAIのインタフェースでアクセスしてみました。

# OPEN APIのREST APIでアクセス
curlコマンドでアクセスしてみます。
```
curl -s 'http://127.0.0.1:8000/v1/chat/completions' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"model": "models/granite-7b-lab-Q4_K_M.gguf", "messages": [ { "role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the highest mountain in the world?" } ]}'
```
以下のような答えが返ってきました！
>{"id":"chatcmpl-7a674e47-74cd-4834-bcb0-bc900fe377a0","object":"chat.completion","created":1723613439,"model":"models/granite-7b-lab-Q4_K_M.gguf","choices":[{"index":0,"message":{"content":"The highest mountain in the world is Mount Everest, which has an elevation of 8,848.86 meters (29,031.7 feet) above sea level. It's located on the border between Nepal and China in the Himalayas.","role":"assistant"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":23,"completion_tokens":61,"total_tokens":84}}

# OPEN AIのSDKでアクセス
次にOpenAIのPythonSDKでアクセスしてみます。
```python:instructlab_openai_stream.py
from openai import OpenAI

client = OpenAI(
    base_url= "http://127.0.0.1:8000/v1/",
    api_key= "myapi",
    max_retries= 3,
    timeout= 1200
)

stream = client.chat.completions.create(
    model= "models/granite-7b-lab-Q4_K_M.gguf",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the highest mountain in the world?" }
    ],
    stream=True,
    )

for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")
```
`api_key`は何かしら指定しないとエラーになるため、適当な文字列を指定しました。

これを実行するとストリーム表示で出力されました！
>$ python InstructLab_openaiSDK_stream.py
The highest mountain in the world is Mount Everest, which has an elevation of approximately 8,848.86 meters (29,031.7 feet) above sea level, as confirmed by a 2020 survey conducted by China and Nepal.

# LangChainのSDKでアクセス
最後にLangChainでアクセスしてみます。
```instructlab_langchain_stream.py
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage

model = ChatOpenAI(
    model_name="models/granite-7b-lab-Q4_K_M.gguf",
    base_url="http://127.0.0.1:8000/v1/",
    api_key="myapi"
)

messages = [
    SystemMessage(content="You are a helpful assistant."),
    HumanMessage(content="What is the highest mountain in the world?")
]

for chunk in model.stream(messages):
    print(chunk.content, end="", flush=True)
```
こちらもアクセスできました。
>$ python InstructLab_langchain_stream.py
The highest mountain in the world is Mount Everest, which has an elevation of 8,848.86 meters (29,031.7 feet) above sea level. It's located on the border between Nepal and China's autonomous region of Tibet.

LangChainにはローカルのGGUFモデルにアクセスする他の方法もありそうなのですが、馴染みのあるAPIで簡単にアクセスできそうなことがわかりました！

# 参考

https://github.com/instructlab/instructlab

https://github.com/abetlen/llama-cpp-python