ドキュメンテーション

ツール使用

ツール使用により、LLM は LM Studio の REST API (または任意の OpenAI クライアント) を介して、/v1/chat/completions エンドポイントを通じて外部関数や API の呼び出しを要求できるようになります。これにより、LLM の機能はテキスト出力の範囲をはるかに超えて拡張されます。


🔔 ツール使用には LM Studio 0.3.6 以降が必要です。こちらから入手してください。

クイックスタート

1. LM Studio をサーバーとして起動する

ご自身のコードから LM Studio をプログラム的に使用するには、LM Studio をローカルサーバーとして実行してください。

LM Studio の「開発者」タブから、または lms CLI を介してサーバーを起動できます。

lms server start
npx lmstudio install-cli を実行して lms をインストールします。

これにより、OpenAI のような REST API を介して LM Studio と対話できるようになります。LM Studio の OpenAI のような API の概要については、「LM Studio をサーバーとして実行する」を参照してください。

2. モデルをロードする

LM Studio の「チャット」または「開発者」タブから、または lms CLI を介してモデルをロードできます。

lms load

3. 例をコピー、貼り付け、実行する!


ツール使用

「ツール使用」とは実際には何か?

ツール使用とは

  • LLM は関数の呼び出しを要求するテキストを出力します(LLM はコードを直接実行できません)。
  • ご自身のコードがそれらの関数を実行します。
  • ご自身のコードがその結果を LLM にフィードバックします。

高レベルのフロー

┌──────────────────────────┐
│ SETUP: LLM + Tool list   │
└──────────┬───────────────┘

┌──────────────────────────┐
│    Get user input        │◄────┐
└──────────┬───────────────┘     │
           ▼                     │
┌──────────────────────────┐     │
│ LLM prompted w/messages  │     │
└──────────┬───────────────┘     │
           ▼                     │
     Needs tools?                │
      │         │                │
    Yes         No               │
      │         │                │
      ▼         └────────────┐   │
┌─────────────┐              │   │
│Tool Response│              │   │
└──────┬──────┘              │   │
       ▼                     │   │
┌─────────────┐              │   │
│Execute tools│              │   │
└──────┬──────┘              │   │
       ▼                     ▼   │
┌─────────────┐          ┌───────────┐
│Add results  │          │  Normal   │
│to messages  │          │ response  │
└──────┬──────┘          └─────┬─────┘
       │                       ▲
       └───────────────────────┘

詳細なフロー

LM Studio は、リクエストボディの tools パラメータに関数定義が与えられた場合、/v1/chat/completions エンドポイントを介したツール使用をサポートしています。ツールは、そのパラメータと使用方法を記述する関数定義の配列として指定されます。例:

これは OpenAI のFunction Calling API と同じ形式に従っており、OpenAI クライアント SDK を介して動作することが期待されます。

この例のフローでは、lmstudio-community/Qwen2.5-7B-Instruct-GGUF をモデルとして使用します。

  • LLM にツールのリストを提供します。これらはモデルが呼び出しを要求できるツールです。例:

    // the list of tools is model-agnostic
    [
      {
        "type": "function",
        "function": {
          "name": "get_delivery_date",
          "description": "Get the delivery date for a customer's order",
          "parameters": {
            "type": "object",
            "properties": {
              "order_id": {
                "type": "string"
              }
            },
            "required": ["order_id"]
          }
        }
      }
    ]
    

    このリストは、モデルのチャットテンプレートに応じて、モデルの system プロンプトに挿入されます。Qwen2.5-Instruct の場合、これは次のようになります。

    <|im_start|>system
    You are Qwen, created by Alibaba Cloud. You are a helpful assistant.
    
    # Tools
    
    You may call one or more functions to assist with the user query.
    
    You are provided with function signatures within <tools></tools> XML tags:
    <tools>
    {"type": "function", "function": {"name": "get_delivery_date", "description": "Get the delivery date for a customer's order", "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}}}
    </tools>
    
    For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
    <tool_call>
    {"name": <function-name>, "arguments": <args-json-object>}
    </tool_call><|im_end|>
    

    重要: LLM は関数、API、その他のツールを直接呼び出すことはできないため、モデルはこれらのツールへの呼び出しを要求することしかできません。LLM はテキストのみを出力し、そのテキストを解析して関数をプログラム的に呼び出すことができます。

  • プロンプトが与えられると、LLM は次のいずれかを決定できます。

    • (a) 1つ以上のツールを呼び出す
    User: Get me the delivery date for order 123
    Model: <tool_call>
    {"name": "get_delivery_date", "arguments": {"order_id": "123"}}
    </tool_call>
    
    • (b) 通常通り応答する
    User: Hi
    Model: Hello! How can I assist you today?
    
  • LM Studio は、モデルからのテキスト出力を OpenAI に準拠した chat.completion 応答オブジェクトに解析します。

    • モデルが tools へのアクセスを与えられた場合、LM Studio はツール呼び出しを chat.completion 応答オブジェクトの response.choices[0].message.tool_calls フィールドに解析しようとします。
    • LM Studio が正しくフォーマットされたツール呼び出しを解析できない場合、応答は標準の response.choices[0].message.content フィールドに返されます。
    • : 小さなモデルやツール使用のために訓練されていないモデルは、不適切な形式のツール呼び出しを出力する可能性があり、その結果、LM Studio はそれらを tool_calls フィールドに解析できません。これは、期待通りに tool_calls が受信されない場合のトラブルシューティングに役立ちます。Qwen2.5-Instruct の不適切にフォーマットされたツール呼び出しの例:
    <tool_call>
    ["name": "get_delivery_date", function: "date"]
    </tool_call>
    

    括弧が正しくなく、呼び出しが name, argument の形式に従っていないことに注意してください。

  • ご自身のコードは、モデルからのツール呼び出しをチェックするために chat.completion 応答を解析し、モデルによって指定されたパラメータで適切なツールを呼び出します。次に、ご自身のコードは次の両方を追加します。

    • モデルのツール呼び出しメッセージ
    • ツール呼び出しの結果

    モデルに送り返す messages 配列に

    # pseudocode, see examples for copy-paste snippets
    if response.has_tool_calls:
        for each tool_call:
            # Extract function name & args
            function_to_call = tool_call.name     # e.g. "get_delivery_date"
            args = tool_call.arguments            # e.g. {"order_id": "123"}
    
            # Execute the function
            result = execute_function(function_to_call, args)
    
            # Add result to conversation
            add_to_messages([
                ASSISTANT_TOOL_CALL_MESSAGE,      # The request to use the tool
                TOOL_RESULT_MESSAGE               # The tool's response
            ])
    else:
        # Normal response without tools
        add_to_messages(response.content)
    
  • LLM は、更新されたメッセージ配列で再度プロンプトを与えられますが、ツールへのアクセスはありません。これは以下の理由によるものです。

    • LLM は会話履歴にツールの結果をすでに持っている
    • LLM にユーザーへの最終応答を提供してほしいのであって、追加のツールを呼び出してほしいわけではない
    # Example messages
    messages = [
        {"role": "user", "content": "When will order 123 be delivered?"},
        {"role": "assistant", "function_call": {
            "name": "get_delivery_date",
            "arguments": {"order_id": "123"}
        }},
        {"role": "tool", "content": "2024-03-15"},
    ]
    response = client.chat.completions.create(
        model="lmstudio-community/qwen2.5-7b-instruct",
        messages=messages
    )
    

    この呼び出し後の response.choices[0].message.content フィールドは次のようになります。

    Your order #123 will be delivered on March 15th, 2024
    
  • ループはフローのステップ2に戻って続行されます

注: これはツール使用の厳密な(pedantic)フローです。ただし、ご自身のユースケースに最も合うようにこのフローを試すことができます。


サポートされているモデル

LM Studio を介して、すべてのモデルが少なくともある程度のツール使用をサポートしています。

ただし、現在、エクスペリエンスの品質に影響を与える可能性のある2つのサポートレベル(ネイティブとデフォルト)があります。

ネイティブツール使用をサポートするモデルは、アプリ内でハンマーバッジが表示され、一般的にツール使用シナリオでより優れたパフォーマンスを発揮します。

ネイティブツール使用サポート

「ネイティブ」なツール使用サポートとは、以下の両方を意味します。

  • モデルがツール使用をサポートするチャットテンプレートを持っていること(通常、モデルがツール使用のために訓練されていることを意味します)
    • これは、tools 配列をシステムプロンプトにフォーマットし、モデルにツール呼び出しのフォーマット方法を伝えるために使用されます。
    • 例: Qwen2.5-Instruct チャットテンプレート
  • LM Studio がそのモデルのツール使用フォーマットをサポートしていること
    • LM Studio がチャット履歴をチャットテンプレートに適切に入力し、モデルが出力するツール呼び出しを chat.completion オブジェクトに解析するために必要です。

現在 LM Studio でネイティブツール使用サポートを持つモデル(変更される可能性があります)

デフォルトのツール使用サポート

「デフォルト」のツール使用サポートとは、以下のいずれかを意味します。

  • モデルがツール使用をサポートするチャットテンプレートを持っていないこと(通常、モデルがツール使用のために訓練されていないことを意味します)
  • LM Studio が現在、そのモデルのツール使用フォーマットをサポートしていないこと

内部的には、デフォルトのツール使用は次のように機能します。

  • モデルにカスタムのシステムプロンプトと使用するデフォルトのツール呼び出しフォーマットを与える
  • tool ロールのメッセージを user ロールに変換し、tool ロールがないチャットテンプレートでも互換性を持たせる
  • assistant ロールの tool_calls をデフォルトのツール呼び出しフォーマットに変換する

結果はモデルによって異なります。

ネイティブツール使用サポートがないモデルに対して、ターミナルで lms log stream を実行し、その後 tools を含むチャット完了リクエストを送信することで、デフォルトのフォーマットを確認できます。デフォルトのフォーマットは変更される可能性があります。

展開してデフォルトのツール使用フォーマットの例を見る
 % lms log stream
Streaming logs from LM Studio

timestamp: 11/13/2024, 9:35:15 AM
type: llm.prediction.input
modelIdentifier: gemma-2-2b-it
modelPath: lmstudio-community/gemma-2-2b-it-GGUF/gemma-2-2b-it-Q4_K_M.gguf
input: "<start_of_turn>system
You are a tool-calling AI. You can request calls to available tools with this EXACT format:
[TOOL_REQUEST]{"name": "tool_name", "arguments": {"param1": "value1"}}[END_TOOL_REQUEST]

AVAILABLE TOOLS:
{
  "type": "toolArray",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_delivery_date",
        "description": "Get the delivery date for a customer's order",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            }
          },
          "required": [
            "order_id"
          ]
        }
      }
    }
  ]
}

RULES:
- Only use tools from AVAILABLE TOOLS
- Include all required arguments
- Use one [TOOL_REQUEST] block per tool
- Never use [TOOL_RESULT]
- If you decide to call one or more tools, there should be no other text in your message

Examples:
"Check Paris weather"
[TOOL_REQUEST]{"name": "get_weather", "arguments": {"location": "Paris"}}[END_TOOL_REQUEST]

"Send email to John about meeting and open browser"
[TOOL_REQUEST]{"name": "send_email", "arguments": {"to": "John", "subject": "meeting"}}[END_TOOL_REQUEST]
[TOOL_REQUEST]{"name": "open_browser", "arguments": {}}[END_TOOL_REQUEST]

Respond conversationally if no matching tools exist.<end_of_turn>
<start_of_turn>user
Get me delivery date for order 123<end_of_turn>
<start_of_turn>model
"

モデルがこのフォーマットに正確に従ってツールを呼び出す場合、つまり

[TOOL_REQUEST]{"name": "get_delivery_date", "arguments": {"order_id": "123"}}[END_TOOL_REQUEST]

その場合、LM Studio はネイティブサポートされるモデルと同様に、それらのツール呼び出しを chat.completions オブジェクトに解析できます。

ネイティブツール使用サポートがないすべてのモデルには、デフォルトのツール使用サポートがあります。


curl を使用した例curl を使用した例'へのリンク">

この例では、モデルが curl ユーティリティを使用してツール呼び出しを要求する方法を示します。

この例を Mac または Linux で実行するには、任意のターミナルを使用してください。Windows では、Git Bash を使用してください。

curl http://localhost:1234/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "lmstudio-community/qwen2.5-7b-instruct",
    "messages": [{"role": "user", "content": "What dell products do you have under $50 in electronics?"}],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "search_products",
          "description": "Search the product catalog by various criteria. Use this whenever a customer asks about product availability, pricing, or specifications.",
          "parameters": {
            "type": "object",
            "properties": {
              "query": {
                "type": "string",
                "description": "Search terms or product name"
              },
              "category": {
                "type": "string",
                "description": "Product category to filter by",
                "enum": ["electronics", "clothing", "home", "outdoor"]
              },
              "max_price": {
                "type": "number",
                "description": "Maximum price in dollars"
              }
            },
            "required": ["query"],
            "additionalProperties": false
          }
        }
      }
    ]
  }'

/v1/chat/completions で認識されるすべてのパラメータが尊重され、利用可能なツールの配列は tools フィールドで提供される必要があります。

モデルがユーザーメッセージをツール呼び出しで満たすのが最適であると判断した場合、ツール呼び出しリクエストオブジェクトの配列が応答フィールド choices[0].message.tool_calls で提供されます。

トップレベルの応答オブジェクトの finish_reason フィールドにも "tool_calls" が入力されます。

上記の curl リクエストに対する応答例は次のようになります。

{
  "id": "chatcmpl-gb1t1uqzefudice8ntxd9i",
  "object": "chat.completion",
  "created": 1730913210,
  "model": "lmstudio-community/qwen2.5-7b-instruct",
  "choices": [
    {
      "index": 0,
      "logprobs": null,
      "finish_reason": "tool_calls",
      "message": {
        "role": "assistant",
        "tool_calls": [
          {
            "id": "365174485",
            "type": "function",
            "function": {
              "name": "search_products",
              "arguments": "{\"query\":\"dell\",\"category\":\"electronics\",\"max_price\":50}"
            }
          }
        ]
      }
    }
  ],
  "usage": {
    "prompt_tokens": 263,
    "completion_tokens": 34,
    "total_tokens": 297
  },
  "system_fingerprint": "lmstudio-community/qwen2.5-7b-instruct"
}

平易な英語で言えば、上記の応答はモデルが次のように言っていると考えることができます。

search_products 関数を引数付きで呼び出してください」

  • query パラメータには 'dell'、
  • category パラメータには 'electronics'、
  • max_price パラメータには '50'

として結果を返してください」

tool_calls フィールドは、実際の関数/API を呼び出すために解析する必要があります。以下の例でその方法を示します。


python を使用した例python を使用した例'へのリンク">

ツール使用は、Python のようなプログラミング言語と組み合わせることで真価を発揮します。Python では、tools フィールドで指定された関数を実装し、モデルが要求したときにそれらをプログラム的に呼び出すことができます。

シングルターンの例

以下は、モデルがコンソールに「Hello」という挨拶を出力する say_hello 関数を呼び出すことを可能にする、シンプルなシングルターン(モデルは1回のみ呼び出される)の例です。

single-turn-example.py

from openai import OpenAI

# Connect to LM Studio
client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")

# Define a simple function
def say_hello(name: str) → str:
    print(f"Hello, {name}!")

# Tell the AI about our function
tools = [
    {
        "type": "function",
        "function": {
            "name": "say_hello",
            "description": "Says hello to someone",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "The person's name"
                    }
                },
                "required": ["name"]
            }
        }
    }
]

# Ask the AI to use our function
response = client.chat.completions.create(
    model="lmstudio-community/qwen2.5-7b-instruct",
    messages=[{"role": "user", "content": "Can you say hello to Bob the Builder?"}],
    tools=tools
)

# Get the name the AI wants to use a tool to say hello to
# (Assumes the AI has requested a tool call and that tool call is say_hello)
tool_call = response.choices[0].message.tool_calls[0]
name = eval(tool_call.function.arguments)["name"]

# Actually call the say_hello function
say_hello(name) # Prints: Hello, Bob the Builder!

このスクリプトをコンソールから実行すると、次のような結果が得られます。

→ % python single-turn-example.py
Hello, Bob the Builder!

名前をいじってみてください

messages=[{"role": "user", "content": "Can you say hello to Bob the Builder?"}]

モデルが異なる名前で say_hello 関数を呼び出すのを確認します。

マルチターンの例

では、もう少し複雑な例を見てみましょう。

この例では、

  • モデルが get_delivery_date 関数を呼び出せるようにする
  • その関数呼び出しの結果をモデルに返し、ユーザーのリクエストをプレーンテキストで満たせるようにする
multi-turn-example.py (クリックして展開)
from datetime import datetime, timedelta
import json
import random
from openai import OpenAI

# Point to the local server
client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")
model = "lmstudio-community/qwen2.5-7b-instruct"


def get_delivery_date(order_id: str) → datetime:
    # Generate a random delivery date between today and 14 days from now
    # in a real-world scenario, this function would query a database or API
    today = datetime.now()
    random_days = random.randint(1, 14)
    delivery_date = today + timedelta(days=random_days)
    print(
        f"\nget_delivery_date function returns delivery date:\n\n{delivery_date}",
        flush=True,
    )
    return delivery_date


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_delivery_date",
            "description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "The customer's order ID.",
                    },
                },
                "required": ["order_id"],
                "additionalProperties": False,
            },
        },
    }
]

messages = [
    {
        "role": "system",
        "content": "You are a helpful customer support assistant. Use the supplied tools to assist the user.",
    },
    {
        "role": "user",
        "content": "Give me the delivery date and time for order number 1017",
    },
]

# LM Studio
response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tools,
)

print("\nModel response requesting tool call:\n", flush=True)
print(response, flush=True)

# Extract the arguments for get_delivery_date
# Note this code assumes we have already determined that the model generated a function call.
tool_call = response.choices[0].message.tool_calls[0]
arguments = json.loads(tool_call.function.arguments)

order_id = arguments.get("order_id")

# Call the get_delivery_date function with the extracted order_id
delivery_date = get_delivery_date(order_id)

assistant_tool_call_request_message = {
    "role": "assistant",
    "tool_calls": [
        {
            "id": response.choices[0].message.tool_calls[0].id,
            "type": response.choices[0].message.tool_calls[0].type,
            "function": response.choices[0].message.tool_calls[0].function,
        }
    ],
}

# Create a message containing the result of the function call
function_call_result_message = {
    "role": "tool",
    "content": json.dumps(
        {
            "order_id": order_id,
            "delivery_date": delivery_date.strftime("%Y-%m-%d %H:%M:%S"),
        }
    ),
    "tool_call_id": response.choices[0].message.tool_calls[0].id,
}

# Prepare the chat completion call payload
completion_messages_payload = [
    messages[0],
    messages[1],
    assistant_tool_call_request_message,
    function_call_result_message,
]

# Call the OpenAI API's chat completions endpoint to send the tool call result back to the model
# LM Studio
response = client.chat.completions.create(
    model=model,
    messages=completion_messages_payload,
)

print("\nFinal model response with knowledge of the tool call result:\n", flush=True)
print(response.choices[0].message.content, flush=True)

このスクリプトをコンソールから実行すると、次のような結果が得られます。

→ % python multi-turn-example.py

Model response requesting tool call:

ChatCompletion(id='chatcmpl-wwpstqqu94go4hvclqnpwn', choices=[Choice(finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage(content=None, refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='377278620', function=Function(arguments='{"order_id":"1017"}', name='get_delivery_date'), type='function')]))], created=1730916196, model='lmstudio-community/qwen2.5-7b-instruct', object='chat.completion', service_tier=None, system_fingerprint='lmstudio-community/qwen2.5-7b-instruct', usage=CompletionUsage(completion_tokens=24, prompt_tokens=223, total_tokens=247, completion_tokens_details=None, prompt_tokens_details=None))

get_delivery_date function returns delivery date:

2024-11-19 13:03:17.773298

Final model response with knowledge of the tool call result:

Your order number 1017 is scheduled for delivery on November 19, 2024, at 13:03 PM.

高度なエージェントの例

上記の原則に基づき、LM Studio モデルとローカルで定義された関数を組み合わせることで、「エージェント」を作成できます。これは、言語モデルとカスタム関数を組み合わせ、リクエストを理解し、基本的なテキスト生成を超えたアクションを実行するシステムです。

以下の例のエージェントは次のことができます。

  • デフォルトブラウザで安全なURLを開く
  • 現在時刻を確認する
  • ファイルシステム内のディレクトリを分析する
agent-chat-example.py (クリックして展開)
import json
from urllib.parse import urlparse
import webbrowser
from datetime import datetime
import os
from openai import OpenAI

# Point to the local server
client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")
model = "lmstudio-community/qwen2.5-7b-instruct"


def is_valid_url(url: str) → bool:

    try:
        result = urlparse(url)
        return bool(result.netloc)  # Returns True if there's a valid network location
    except Exception:
        return False


def open_safe_url(url: str) → dict:
    # List of allowed domains (expand as needed)
    SAFE_DOMAINS = {
        "lmstudio.ai",
        "github.com",
        "google.com",
        "wikipedia.org",
        "weather.com",
        "stackoverflow.com",
        "python.org",
        "docs.python.org",
    }

    try:
        # Add http:// if no scheme is present
        if not url.startswith(('http://', 'https://')):
            url = 'http://' + url

        # Validate URL format
        if not is_valid_url(url):
            return {"status": "error", "message": f"Invalid URL format: {url}"}

        # Parse the URL and check domain
        parsed_url = urlparse(url)
        domain = parsed_url.netloc.lower()
        base_domain = ".".join(domain.split(".")[-2:])

        if base_domain in SAFE_DOMAINS:
            webbrowser.open(url)
            return {"status": "success", "message": f"Opened {url} in browser"}
        else:
            return {
                "status": "error",
                "message": f"Domain {domain} not in allowed list",
            }
    except Exception as e:
        return {"status": "error", "message": str(e)}


def get_current_time() → dict:
    """Get the current system time with timezone information"""
    try:
        current_time = datetime.now()
        timezone = datetime.now().astimezone().tzinfo
        formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S %Z")
        return {
            "status": "success",
            "time": formatted_time,
            "timezone": str(timezone),
            "timestamp": current_time.timestamp(),
        }
    except Exception as e:
        return {"status": "error", "message": str(e)}


def analyze_directory(path: str = ".") → dict:
    """Count and categorize files in a directory"""
    try:
        stats = {
            "total_files": 0,
            "total_dirs": 0,
            "file_types": {},
            "total_size_bytes": 0,
        }

        for entry in os.scandir(path):
            if entry.is_file():
                stats["total_files"] += 1
                ext = os.path.splitext(entry.name)[1].lower() or "no_extension"
                stats["file_types"][ext] = stats["file_types"].get(ext, 0) + 1
                stats["total_size_bytes"] += entry.stat().st_size
            elif entry.is_dir():
                stats["total_dirs"] += 1
                # Add size of directory contents
                for root, _, files in os.walk(entry.path):
                    for file in files:
                        try:
                            stats["total_size_bytes"] += os.path.getsize(os.path.join(root, file))
                        except (OSError, FileNotFoundError):
                            continue

        return {"status": "success", "stats": stats, "path": os.path.abspath(path)}
    except Exception as e:
        return {"status": "error", "message": str(e)}


tools = [
    {
        "type": "function",
        "function": {
            "name": "open_safe_url",
            "description": "Open a URL in the browser if it's deemed safe",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "The URL to open",
                    },
                },
                "required": ["url"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "Get the current system time with timezone information",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": [],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "analyze_directory",
            "description": "Analyze the contents of a directory, counting files and folders",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "The directory path to analyze. Defaults to current directory if not specified.",
                    },
                },
                "required": [],
            },
        },
    },
]


def process_tool_calls(response, messages):
    """Process multiple tool calls and return the final response and updated messages"""
    # Get all tool calls from the response
    tool_calls = response.choices[0].message.tool_calls

    # Create the assistant message with tool calls
    assistant_tool_call_message = {
        "role": "assistant",
        "tool_calls": [
            {
                "id": tool_call.id,
                "type": tool_call.type,
                "function": tool_call.function,
            }
            for tool_call in tool_calls
        ],
    }

    # Add the assistant's tool call message to the history
    messages.append(assistant_tool_call_message)

    # Process each tool call and collect results
    tool_results = []
    for tool_call in tool_calls:
        # For functions with no arguments, use empty dict
        arguments = (
            json.loads(tool_call.function.arguments)
            if tool_call.function.arguments.strip()
            else {}
        )

        # Determine which function to call based on the tool call name
        if tool_call.function.name == "open_safe_url":
            result = open_safe_url(arguments["url"])
        elif tool_call.function.name == "get_current_time":
            result = get_current_time()
        elif tool_call.function.name == "analyze_directory":
            path = arguments.get("path", ".")
            result = analyze_directory(path)
        else:
            # llm tried to call a function that doesn't exist, skip
            continue

        # Add the result message
        tool_result_message = {
            "role": "tool",
            "content": json.dumps(result),
            "tool_call_id": tool_call.id,
        }
        tool_results.append(tool_result_message)
        messages.append(tool_result_message)

    # Get the final response
    final_response = client.chat.completions.create(
        model=model,
        messages=messages,
    )

    return final_response


def chat():
    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant that can open safe web links, tell the current time, and analyze directory contents. Use these capabilities whenever they might be helpful.",
        }
    ]

    print(
        "Assistant: Hello! I can help you open safe web links, tell you the current time, and analyze directory contents. What would you like me to do?"
    )
    print("(Type 'quit' to exit)")

    while True:
        # Get user input
        user_input = input("\nYou: ").strip()

        # Check for quit command
        if user_input.lower() == "quit":
            print("Assistant: Goodbye!")
            break

        # Add user message to conversation
        messages.append({"role": "user", "content": user_input})

        try:
            # Get initial response
            response = client.chat.completions.create(
                model=model,
                messages=messages,
                tools=tools,
            )

            # Check if the response includes tool calls
            if response.choices[0].message.tool_calls:
                # Process all tool calls and get final response
                final_response = process_tool_calls(response, messages)
                print("\nAssistant:", final_response.choices[0].message.content)

                # Add assistant's final response to messages
                messages.append(
                    {
                        "role": "assistant",
                        "content": final_response.choices[0].message.content,
                    }
                )
            else:
                # If no tool call, just print the response
                print("\nAssistant:", response.choices[0].message.content)

                # Add assistant's response to messages
                messages.append(
                    {
                        "role": "assistant",
                        "content": response.choices[0].message.content,
                    }
                )

        except Exception as e:
            print(f"\nAn error occurred: {str(e)}")
            exit(1)


if __name__ == "__main__":
    chat()

このスクリプトをコンソールから実行すると、エージェントとチャットできます。

→ % python agent-example.py
Assistant: Hello! I can help you open safe web links, tell you the current time, and analyze directory contents. What would you like me to do?
(Type 'quit' to exit)

You: What time is it?

Assistant: The current time is 14:11:40 (EST) as of November 6, 2024.

You: What time is it now?

Assistant: The current time is 14:13:59 (EST) as of November 6, 2024.

You: Open lmstudio.ai

Assistant: The link to lmstudio.ai has been opened in your default web browser.

You: What's in my current directory?

Assistant: Your current directory at `/Users/matt/project` contains a total of 14 files and 8 directories. Here's the breakdown:

- Files without an extension: 3
- `.mjs` files: 2
- `.ts` (TypeScript) files: 3
- Markdown (`md`) file: 1
- JSON files: 4
- TOML file: 1

The total size of these items is 1,566,990,604 bytes.

You: Thank you!

Assistant: You're welcome! If you have any other questions or need further assistance, feel free to ask.

You:

ストリーミング

/v1/chat/completions (stream=true) を介してストリーミングを行う際、ツール呼び出しはチャンクで送信されます。関数名と引数は、chunk.choices[0].delta.tool_calls.function.name および chunk.choices[0].delta.tool_calls.function.arguments を介して断片的に送信されます。

例えば、get_current_weather(location="San Francisco") を呼び出す場合、各 chunk.choices[0].delta.tool_calls[0] オブジェクトのストリーミングされた ChoiceDeltaToolCall は次のようになります。

ChoiceDeltaToolCall(index=0, id='814890118', function=ChoiceDeltaToolCallFunction(arguments='', name='get_current_weather'), type='function')
ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='{"', name=None), type=None)
ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='location', name=None), type=None)
ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='":"', name=None), type=None)
ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='San Francisco', name=None), type=None)
ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='"}', name=None), type=None)

これらのチャンクは、実行のための完全な関数シグネチャを形成するために、ストリーム全体で累積される必要があります。

以下の例は、/v1/chat/completions ストリーミングエンドポイント (stream=true) を介して、シンプルなツール強化型チャットボットを作成する方法を示しています。

tool-streaming-chatbot.py (クリックして展開)
from openai import OpenAI
import time

client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio")
MODEL = "lmstudio-community/qwen2.5-7b-instruct"

TIME_TOOL = {
    "type": "function",
    "function": {
        "name": "get_current_time",
        "description": "Get the current time, only if asked",
        "parameters": {"type": "object", "properties": {}},
    },
}

def get_current_time():
    return {"time": time.strftime("%H:%M:%S")}

def process_stream(stream, add_assistant_label=True):
    """Handle streaming responses from the API"""
    collected_text = ""
    tool_calls = []
    first_chunk = True

    for chunk in stream:
        delta = chunk.choices[0].delta

        # Handle regular text output
        if delta.content:
            if first_chunk:
                print()
                if add_assistant_label:
                    print("Assistant:", end=" ", flush=True)
                first_chunk = False
            print(delta.content, end="", flush=True)
            collected_text += delta.content

        # Handle tool calls
        elif delta.tool_calls:
            for tc in delta.tool_calls:
                if len(tool_calls) <= tc.index:
                    tool_calls.append({
                        "id": "", "type": "function",
                        "function": {"name": "", "arguments": ""}
                    })
                tool_calls[tc.index] = {
                    "id": (tool_calls[tc.index]["id"] + (tc.id or "")),
                    "type": "function",
                    "function": {
                        "name": (tool_calls[tc.index]["function"]["name"] + (tc.function.name or "")),
                        "arguments": (tool_calls[tc.index]["function"]["arguments"] + (tc.function.arguments or ""))
                    }
                }
    return collected_text, tool_calls

def chat_loop():
    messages = []
    print("Assistant: Hi! I am an AI agent empowered with the ability to tell the current time (Type 'quit' to exit)")

    while True:
        user_input = input("\nYou: ").strip()
        if user_input.lower() == "quit":
            break

        messages.append({"role": "user", "content": user_input})

        # Get initial response
        response_text, tool_calls = process_stream(
            client.chat.completions.create(
                model=MODEL,
                messages=messages,
                tools=[TIME_TOOL],
                stream=True,
                temperature=0.2
            )
        )

        if not tool_calls:
            print()

        text_in_first_response = len(response_text) > 0
        if text_in_first_response:
            messages.append({"role": "assistant", "content": response_text})

        # Handle tool calls if any
        if tool_calls:
            tool_name = tool_calls[0]["function"]["name"]
            print()
            if not text_in_first_response:
                print("Assistant:", end=" ", flush=True)
            print(f"**Calling Tool: {tool_name}**")
            messages.append({"role": "assistant", "tool_calls": tool_calls})

            # Execute tool calls
            for tool_call in tool_calls:
                if tool_call["function"]["name"] == "get_current_time":
                    result = get_current_time()
                    messages.append({
                        "role": "tool",
                        "content": str(result),
                        "tool_call_id": tool_call["id"]
                    })

            # Get final response after tool execution
            final_response, _ = process_stream(
                client.chat.completions.create(
                    model=MODEL,
                    messages=messages,
                    stream=True
                ),
                add_assistant_label=False
            )

            if final_response:
                print()
                messages.append({"role": "assistant", "content": final_response})

if __name__ == "__main__":
    chat_loop()

このスクリプトをコンソールから実行すると、ボットとチャットできます

→ % python tool-streaming-chatbot.py
Assistant: Hi! I am an AI agent empowered with the ability to tell the current time (Type 'quit' to exit)

You: Tell me a joke, then tell me the current time

Assistant: Sure! Here's a light joke for you: Why don't scientists trust atoms? Because they make up everything.

Now, let me get the current time for you.

**Calling Tool: get_current_time**

The current time is 18:49:31. Enjoy your day!

You:

コミュニティ

他の LM Studio ユーザーとチャットしたり、LLM、ハードウェアなどについてLM Studio Discord サーバーで議論したりできます。