日本語FunctionCallingデータセットを作る【ローカルLLM】

ローカルLLMにFunctionCalling機能を付与するための合成データセットを作成したので、ざっくりとした作り方を書きます。  

# 作成したもの
といっても1000件くらいしかありません。

https://huggingface.co/datasets/SousiOmine/Japanese-Pythonic-FunctionCall

おまけ FunctionCallingに対応させたSarashina2.2-3b-instruct

https://huggingface.co/SousiOmine/sarashina2.2-3b-instruct-v0.1-Pythonic-FunctionCall

# 前置き
ローカルLLMでFunctionCallingとかツール呼び出しができるモデルは少なく、私の知る限りだとQwen、Aya-expanse、Mistral-v0.3、llama3くらいしかありません。~~思ってたより多いな~~  
日本語能力が高く、かつ出力の使用に制約のないFunctionCalling対応モデルを作成するためにデータセットを作ります。

# 関数呼び出しの方法について
LLMが関数を呼び出す方法として、関数名や引数をJSONオブジェクトで書く手法がよく使われています。  

https://huggingface.co/datasets/madroid/glaive-function-calling-openai

これのようなOpenAIのクローズLLM製データセットを見るとイメージしやすいと思います。  
ただしJSONはパラメータの小さい（特に7B未満の）ローカルLLMが書くには構造が重たそう＆データセット作成もめんどくさそうでなにかいい方法はないかと調べたところ、vllmのページにpython関数で呼び出す手法が乗っていました。  

https://docs.vllm.ai/en/latest/features/tool_calling.html#models-with-pythonic-tool-calls-pythonic

今回はこのpython関数方式でFunctionCallingを実現します。JSON方式で作成したい方の参考にならなかったらごめんなさい

# 作成
今回説明するデータセット作成には以下2つが必要になります。
- そこそこ高性能なLLM
- システムプロンプト & ユーザーからのクエリのペア

とてもおおまかなフローチャート
```mermaid
graph TD;
    start
    genpy[システムプロンプトからpython関数定義を作成]
    callfunc[会話履歴を見て、関数を使うか+どのツールを使うのかを判定]
    genresult[関数の戻り値を適当に生成]
    genans[回答を生成]

    start --> genpy
    genpy --> callfunc
    callfunc -->|関数使う| genresult
    callfunc -->|関数不要| genans
    genresult --> callfunc
```
システムプロンプト&ユーザークエリのあるデータセットとLLMを動かすお金があればたくさんデータセットを生成できます。また並列、複数回にわたる関数呼び出しデータを作成できます。

### システムプロンプトからpython関数定義を作成
LLMに渡して使ってもらう関数をLLMに生成してもらいます。  
システムプロンプトもなにも与えずに生成してもらったり、ユーザーからのメッセージから生成してもいいのですが、関数の多様性を確保しつつ、ユーザーの要求に過度に適合しない関数も同時に作成したいと考えシステムプロンプトを参考に関数を用意してもらうことにしました。
```javascript
async function gen_pydefs(system_prompt)
{
    const prompt = `とあるAIには、以下のような役割や指示が割り当てられています。

# あるAIに与えられた指示
「${system_prompt}」
    
このようなAIに関数呼び出し機能を与えます。以下のような関数をいくつか定義してください。
- pythonの関数定義をdocstring（Googleスタイル）で行うこと。
- 問題解決に必要そうな関数を2~8個定義すること。
- 実装は省略し、関数定義のみを記載すること。
- 関数名と引数名は英語で、docstringは日本語または英語で記載すること。
- docstringによる引数の解説では、引数の例を提示するといいでしょう。

以下のような関数の機能を参考にしてください。
- Web検索
- 時間や為替、天気の取得
- コマンドやスクリプト言語の実行
- ファイル操作
- 機器の制御
- その他独自の関数
    `

    // OpenAI クライアントの初期化
    const openai = new OpenAI({
        baseURL: "https://openrouter.ai/api/v1",
        apiKey: process.env.OPENROUTER_API_KEY,
    });

    const result = await openai.chat.completions.create({
        model: function_model,
        messages: [
            { role: "system", content: "あなたは親切なAIアシスタントです。ユーザーからの指示に従い、日本語で回答します。" },
            { role: "user", content: prompt }
        ],
        max_tokens: 8000
    });

    ///コードブロック内のテキストを抽出
    const regex = /```python\s*([\s\S]*?)```/g;
    const matches = [];
    let match;
    while ((match = regex.exec(result.choices[0].message.content)) !== null) {
        matches.push(match[1]);
    }

    return matches.join('\n');
}
```
最近のLLMはなにも言わずともMarkdown形式で出力してくれるので、出力のうちコードブロックの中身のみを抽出しています。なおたまに素のテキストで出力された場合などで抽出に失敗して空文字を返しちゃうこともあるので、同じ手法をとる方がいればもうちょっといい感じに調整してください。

システムプロンプトとして`あなたはAIアシスタントです。ユーザーからの質問に答える際は、対話の流れを自然に保ちつつ、適切な情報を提供し、ユーザーが求める具体的なアドバイスを提供するようにしてください。また、必要に応じてユーモアや感情を加えると良いでしょう。`を与えると
<details><summary>生成されたpython関数</summary>

```py:生成されたpython関数
def perform_web_search(query: str) -> list:
    """
    ウェブ検索を実行します。

    Args:
        query (str): 検索クエリ。例： "東京の天気"

    Returns:
        list: 検索結果のリスト。各要素はウェブページのタイトルとURLです。
    """
    # 実装は省略


def get_weather(city: str) -> dict:
    """
    指定した都市の現在の天気情報を取得します。

    Args:
        city (str): 天気を確認したい都市の名前。例： "東京"

    Returns:
        dict: 天気情報の辞書。{'temperature': 20.5, 'condition': 'Cloudy', 'humidity': 72}
    """
    # 実装は省略
 

def execute_script(script: str) -> str:
    """
    ユーザから受け取ったスクリプトまたはコマンドを実行します。

    Args:
        script (str): 実行したいコマンドまたはスクリプト。例： "ls -la"

    Returns:
        str: 実行結果の出力またはエラーメッセージ。
    """
    # 実装は省略

 
def read_file_content(file_path: str) -> str:
    """
    指定されたファイルの内容を読み取ります。

    Args:
        file_path (str): ファイルへのパス。例： "documents/report.txt"

    Returns:
        str: ファイルの内容。
    """
    # 実装は省略

 
def get_currency_rate(base_currency: str, target_currency: str) -> float:
    """
    指定された通貨間の現在の為替レートを取得し、数値を返します。

    Args:
        base_currency (str): 基準通貨。例： "JPY"
        target_currency (str): 対象通貨。例： "USD"

    Returns:
        float: 1単位の基準通貨を対象通貨に変換した際の値。
    """
    # 実装は省略
 

def get_time_in_zone(timezone: str) -> str:
    """
    指定されたタイムゾーンの現在の時間を取得します。

    Args:
        timezone (str): タイムゾーン。例： "Asia/Tokyo"

    Returns:
        str: 指定されたタイムゾーンの現在時刻。形式は'YYYY-MM-DD HH:MM:SS'。
    """
    # 実装は省略

  
def control_device(device_id: str, action: str) -> bool:
    """
    指定されたデバイスIDに指定されたアクションを適用します。

    Args:
        device_id (str): 機器識別名。例： "KitchenLamp1"
        action (str): 実行するアクション。例： "turn_on", "turn_off", "increase_brightness"

    Returns:
        bool: アクションが成功したかどうか。
    """
    # 実装は省略
 

def provide_advice(topic: str) -> str:
    """
    ユーザが求める特定のトピックに関するアドバイスを提供します。

    Args:
        topic (str): アドバイスを提供したいトピック。例："time management", "healthy diet"

    Returns:
        str: 特定トピックに関して提供するアドバイス。
    """
    # 実装は省略
```
</details>

というようなdocstring付の関数定義を生成してくれます。  
`# 実装は省略`というのだけ余計に見えますね。passとかでいいのですが、ここはお好みにデータ生成側のプロンプトをいじって調整します。

# 会話履歴を見て、関数を使うか+どの関数を使うのかを判定

過去の会話と与えられた関数を見て、次に呼び出す関数を書き出してもらいます。もし呼び出すべき関数がない場合は"None"という文字列を返してもらいます。  

```javascript:関数呼び出し要求生成コード
async function gen_callfunc(talk_turns, pydefs) {
    const prompt = `とあるAIは、このような対話やタスク処理を行っていました。

${talk_turns.map(t => `${t.role}: ${t.content}`).join('\n')}



そしてこのAIには、任意で呼び出せるpython関数が与えられています。
# 関数定義
${pydefs}

もしあなたであれば、この後どのような関数呼び出しを行うかを考えて、以下の形式で教えてください。

# 検討
どのような関数を呼び出すべきか検討

<call_func>関数名(引数1=値1, 引数2=値2, ...), 必要であれば関数名2(引数1=値1, 引数2=値2, ...)</call_func>

# 注意点
- 呼び出したい関数が1つも存在しない（関数呼び出しが不要になった）場合は、<call_func>None</call_func>と返してください。1つでも呼び出したい関数があればNoneと書く必要はありません。
- 関数名や引数名は定義されたものを使用すること。関数定義にないものを使ってはいけません。
- 同じ関数を一度に複数回呼び出すことも可能です。ただし引数は異なる必要があります。
- 以前に呼び出した関数を依然と同じ引数でもう一度実行しても結果は変わらず無駄ですが、引数を変えることで別の結果を得ることができます。一回目の試行で失敗しても引数を変えて再挑戦できます。
- 関数を呼び出す際には、過去に同じ関数同じ引数で呼び出していないかを検討の段階で必ず確認してください。過去呼び出したことのある関数を同じ引数で呼び出すことは禁止します。
- 関数を複数回に分けて呼び出すことは、前回の関数の実行結果を見て実行内容を変更したいときに有用です。
- Userからのクエリに忠実に回答することを忘れないでください。
`

    // OpenAI クライアントの初期化
    const openai = new OpenAI({
        baseURL: "https://openrouter.ai/api/v1",
        apiKey: process.env.OPENROUTER_API_KEY,
    });

    const result = await openai.chat.completions.create({
        model: function_model,
        messages: [
            {role: "system", content: "あなたは親切なAIアシスタントです。ユーザーからの指示に従い、日本語で回答します。"},
            { role: "user", content: prompt }
        ],
        max_tokens: 8000
    });

    if(!result.choices[0].message.content.includes("<call_func>")) {
        return "None";
    }

    // コードブロック内のテキストを抽出
    const regex = /<call_func>(.*?)<\/call_func>/gs;
    const matches = [];
    let match;
    while ((match = regex.exec(result.choices[0].message.content)) !== null) {
        matches.push(match[1]);
    }

    let str = matches.join(',');
    const lastParenIndex = str.lastIndexOf(')');
    str = str.slice(0, lastParenIndex + 1);

    if(str.includes("None") || prompt.includes(str) || lastParenIndex == -1) {
        return "None";
    }
    return str;
}
```

`<call_func>`というタグの中に呼び出す関数を記述してもらい、その中のテキストを抽出しています。ただしデータ生成に用いるLLMの指示追従性が低い場合では余計な出力が結構入ってしまう（例:`web_search(query="aaa"), None` 関数を呼び出すのか呼び出さないのかわからない)ので、最後の)以後の文字列を消したりしています。  

また、過去に呼び出したことのある関数を過去と全く同じ引数で呼び出してしまう事象が頻発しました。同じ関数を同じ引数で繰り返し呼び出すような動作は望ましくないためなるべく避けたいです。プロンプトをいじっても改善しなかったため、抜き出した関数呼び出しの文字列が会話履歴に含まれていた場合に関数呼び出しを行わないこととしました。（上記の実装では完壁ではないのですが、面倒くさくて妥協してしまいました）

例は面倒なので省略！

# 関数の戻り値を適当に生成
さっき作成したpython関数定義では実装は含まれません。実装も作ってもらって実行...なんてやっていたらきりがありませんからね。  
関数の戻り値の項目は次の関数呼び出しの有無決定や回答の生成に必要ですが、作成したデータを実際に訓練させたLLMではこの項目は推論するわけではなく、関数の実行結果を入力として受け取るだけです。  

実際に推論しないんなら戻り値は適当で大丈夫だろ！という割り切りで、データ生成においては関数定義と引数をLLMに渡して戻り値を想像してもらいます。  

```javascript:戻り値適当生成
async function simulation_func_response(pydefs, callfunc) {
    const prompt = `以下のようなpythonの関数があったとします。
${pydefs}

定義された関数を
${callfunc}
このように呼び出したときの戻り値を想像して、以下の形式で教えてください。
<results>戻り値,戻り値2,...</results>

注意点
- 戻り値は複数ある場合があります。複数あった場合でもすべてresultsタグの中にまとめて記載してください。
- 戻り値は省略せず、具体的な値を想像で記載してください。
    `

    // OpenAI クライアントの初期化
    const openai = new OpenAI({
        baseURL: "https://openrouter.ai/api/v1",
        apiKey: process.env.OPENROUTER_API_KEY,
    });

    const result = await openai.chat.completions.create({
        model: function_model,
        messages: [
            {role: "system", content: "あなたは親切なAIアシスタントです。"},
            { role: "user", content: prompt }
        ],
        max_tokens: 8000
    });

    // コードブロック内のテキストを抽出
    const regex = /<results>(.*?)<\/results>/gs;
    const matches = [];
    let match;
    while ((match = regex.exec(result.choices[0].message.content)) !== null) {
        matches.push(match[1]);
    }

    return matches.join(',');
}
```
さっの関数判定と同じ手法で、<results>タグ内に実行結果をまとめて書いてもらうようお願いしました。  
データセットのrole=tool_responseのところを見てもらうと、他のロールと比較して戻り値だけ生成品質が低いことがわかると思います。でも重要なのは戻り値の値から次の回答を考えることであって戻り値の正確性じゃない...といいのですが。

### 回答を生成
関数を使わない判定（さっきの例だと"None"）が返ってきたらユーザーに表示する最終回答を生成します。
あとはここまでのシステムプロンプト、ユーザーからのクエリ、関数呼び出し、戻り値の履歴を全部まとめて最終的な回答を生成してもらうだけです。  

```javascript:回答生成
async function gen_final_answer(talk_turns)
{
    const prompt = `以下の対話履歴を元に、最終的な回答を作成してください。あまり堅苦しくならず、Userのクエリに誠実に従ってください。
${talk_turns.map(t => `${t.role}: ${t.content}`).join('\n')}
    `

    // OpenAI クライアントの初期化
    const openai = new OpenAI({
        baseURL: answer_openai_baseurl,
        apiKey: answer_openai_api_key,
    });

    const result = await openai.chat.completions.create({
        model: answer_model,
        messages: [
            { role: "user", content: prompt }
        ],
        max_tokens: 8000
    });

    return result.choices[0].message.content;
}
```

そしてここまで紹介した処理を欲しいだけ行えばOKです。  

# 生成に使用したデータセット&LLM
この手法で必要なシステムプロンプト&ユーザーからのクエリのペアは、[Kendamaron様のjimba-instruction-all](https://huggingface.co/datasets/Kendamarron/jimba-instruction-all)の中から、出自がKendamarron/system-instruction-calm3-chat-formatのデータを使用させていただきました。システムプロンプトが多様なのがありがたいですね。

関数定義や呼び出し、回答生成を行うLLMについては悩みました。

:::note warn
OpenAIやClaude、Geminiといったモデルは、出力を自社製品の対抗となるモデルへの学習に使用することを禁じています。
:::

以下のような条件でモデルを選定しました。
- APIで使用可能
- 出力の使用に規制がかからない
- ハルシネーションがなるべく少ない
- 日本語が堪能

この結果、関数定義と呼び出し判定、戻り値想像には中国アリババ製のQwen2.5-32B-Instructを、回答生成には米マイクロソフト製のPhi-4を使用することとしました。

2つのモデルを使い分けたのは、前述の条件を満たすLLMが私の知る限り存在しないためです。
OpenAI-o1相当の性能をもつLLMであるDeepseek-R1の重みがMITライセンスで公開されているのですが、R1は唯一ハルシネーションが頻発するという問題があり採用に至りませんでした（ウェブで検索して得た情報と全く関係ない事柄を喋ったりする）。
高性能かつ出力の使用に規制がかからないLLMはそんなになくて、ChatGPTはもちろん、オープンソースを名乗るLlamaや一部Mistralモデルもライセンスで出力の使用に制約がかけられています。~~個人的にはLLMの出力に権利を主張するのは不当だと思う~~

Qwen32Bは日本語がやや不自然で出力に中国語が混じるという問題が、Phi-4は指示追従性が低いという問題があるものの、関数定義が中国語になっていても支障はない！情報を与えて答えを書くだけならなんでもいい！と割り切り採用しました。

日本語系はサイバーエージェントのCalm3-22bが指示追従性、日本語の自然さ共にとても優秀で使いたいのですが、どこもホストしてくれないんですよね...。

# 完成！

https://huggingface.co/datasets/SousiOmine/Japanese-Pythonic-FunctionCall

データセットができました！
OpenRouterのAPIを使ったのですが、データセット作成プログラムを作る途中の試行錯誤も含めて8ドルくらいかかりました。  

# おまけ sarashina2.2をQloraファインチューニングでFunctionCalling対応させる

https://huggingface.co/SousiOmine/sarashina2.2-3b-instruct-v0.1-Pythonic-FunctionCall

unslothを使ってソフトバンク開発のsarashina2.2をpython関数呼び出しに対応させました。3Bと小さいモデルなので、QloraならVRAM8GBのGeforceグラフィックボードでもファインチューニングできます（多分）。

関数なしの際の性能低下を抑えるために、関数なしの普通のinstructionデータも半分ちょい混ぜて2epoch学習しました。

なおsarashina2.2のチャットテンプレートはJSON形式での関数呼び出しに対応しているのですが、これをpython形式に対応させる方法がわからなかったのでチャットテンプレートはそのままです。

<details><summary>学習させたテキストの例</summary>

```

あなたはAIアシスタントです。ユーザーからの質問に答える際は、対話の流れを自然に保ちつつ、適切な情報を提供し、ユーザーが求める具体的なアドバイスを提供するようにしてください。また、必要に応じてユーモアや感情を加えると良いでしょう。

#ツール
ユーザーの要望達成を支援するために、1つ以上のpython関数を呼び出すことができます。
呼び出せるpythonの関数を<tools></tools>タグ内に記します。

<tools>
def perform_web_search(query: str) -> list:
    """
    ウェブ検索を実行します。

    Args:
        query (str): 検索クエリ。例： "東京の天気"

    Returns:
        list: 検索結果のリスト。各要素はウェブページのタイトルとURLです。
    """
    # 実装は省略


def get_weather(city: str) -> dict:
    """
    指定した都市の現在の天気情報を取得します。

    Args:
        city (str): 天気を確認したい都市の名前。例： "東京"

    Returns:
        dict: 天気情報の辞書。{'temperature': 20.5, 'condition': 'Cloudy', 'humidity': 72}
    """
    # 実装は省略
 

def execute_script(script: str) -> str:
    """
    ユーザから受け取ったスクリプトまたはコマンドを実行します。

    Args:
        script (str): 実行したいコマンドまたはスクリプト。例： "ls -la"

    Returns:
        str: 実行結果の出力またはエラーメッセージ。
    """
    # 実装は省略

 
def read_file_content(file_path: str) -> str:
    """
    指定されたファイルの内容を読み取ります。

    Args:
        file_path (str): ファイルへのパス。例： "documents/report.txt"

    Returns:
        str: ファイルの内容。
    """
    # 実装は省略

 
def get_currency_rate(base_currency: str, target_currency: str) -> float:
    """
    指定された通貨間の現在の為替レートを取得し、数値を返します。

    Args:
        base_currency (str): 基準通貨。例： "JPY"
        target_currency (str): 対象通貨。例： "USD"

    Returns:
        float: 1単位の基準通貨を対象通貨に変換した際の値。
    """
    # 実装は省略
 

def get_time_in_zone(timezone: str) -> str:
    """
    指定されたタイムゾーンの現在の時間を取得します。

    Args:
        timezone (str): タイムゾーン。例： "Asia/Tokyo"

    Returns:
        str: 指定されたタイムゾーンの現在時刻。形式は'YYYY-MM-DD HH:MM:SS'。
    """
    # 実装は省略

  
def control_device(device_id: str, action: str) -> bool:
    """
    指定されたデバイスIDに指定されたアクションを適用します。

    Args:
        device_id (str): 機器識別名。例： "KitchenLamp1"
        action (str): 実行するアクション。例： "turn_on", "turn_off", "increase_brightness"

    Returns:
        bool: アクションが成功したかどうか。
    """
    # 実装は省略
 

def provide_advice(topic: str) -> str:
    """
    ユーザが求める特定のトピックに関するアドバイスを提供します。

    Args:
        topic (str): アドバイスを提供したいトピック。例："time management", "healthy diet"

    Returns:
        str: 特定トピックに関して提供するアドバイス。
    """
    # 実装は省略

</tools>

関数を呼び出すには、以下のように関数名と引数を<tool_call></tool_call>タグ内に記述してください。
<tool_call>
function_name(param1=value1, param2=value2)
</tool_call>


最近、仕事が忙しくてなかなか読書の時間が取れないんです。どうすれば読書の習慣を取り戻せますか？

<tool_call>provide_advice(topic="reading habit")</tool_call>

<tool_response>"毎日一定の時間に読書時間を設けて、習慣化を目指しましょう。", "最初は短い時間から始め、徐々に時間を延ばしていくこともおすすめです。"</tool_response>

お疲れさまです！今日は少し落ち着いた時間が欲しいところですよね。仕事が忙しくても、だんだんと読書の習慣を取り戻す方法はいくつかあります。

まず、自分の日課にちょっとした変化を加えることから始めてみましょう。毎日決まった時間に5〜10分だけでも読書する時間を設けてみてください。コーヒーを飲みながら寝る前に、または朝の一息で少しだけ世界に触れる時間を作るのも良いでしょう。これが、いつの間にか習慣になります。

あと、最初は短い時間から始めてみてください。何十ページも読めないかもしれませんし、ページを繰るだけでも構いません。読んだページ数を増やしていく楽しさも味わいながら、徐々に読書時間を延ばしていくと良いですね。

たとえ短い時間でも、その時間を大切に楽しむことが大事ですよ！なんて、ちょっとしたストーリーに浸る時間を積み重ねていくだけで、すぐに読書が心の安らぎになると思います。頑張ってくださいね！忙しい中でも、きっとあなたに合った素敵なランチタイムになるはずです。そうそう、読書のちょっとしたヒントもたくさん見つかるかもしれないですよ！お互い読書を楽しみましょうね♪



```
</details>


このモデルで関数呼び出しなしで普通の質問をしてみます。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/6ca7c1fb-8d5d-4d85-93ad-4f093d22af54.png)
（sarashnaトークナイザのチャットテンプレートは改行しないためか画像に全部入らん）

User「今日のAIニュースと埼玉の天気を教えて」
Assistant「今日のAIニュースとしては、特に注目に値するのは、Googleが「Google Assistant」のアップデートを発表し、新しい機能として「Google Assistant for Work」というビジネス向けのアシスタントを提供し始めたことです。このアップデートでは、企業向けにカスタマイズされた機能が追加されており、例えば、会議のスケジュール調整やドキュメントの管理、チームのタスク管理などが容易に行えるようになっています。

埼玉の天気については、現在（2023年10月時点）の埼玉県の天気は晴れで、気温は約18度から22度程度です。湿度はおよそ50%から60%で、比較的快適な一日となるでしょう。ただし、朝晩は少し冷え込む可能性があるため、暖かい服装を用意することをおすすめします。」  

学習した知識のなかで新しい情報を教えてくれたようですが、もちろん最新情報ではありません。  

次に関数を３つ与えてみることにします。必要そうな関数2つといらなそうな関数1つです。

<details><summary>与えた関数定義</summary>

```python:与えた関数定義
tools = """def perform_web_search(query: str) -> list:
    \"\"\"
    ウェブ検索を実行します。

    Args:
        query (str): 検索クエリ。例： "東京の天気"

    Returns:
        list: 検索結果のリスト。各要素はウェブページのタイトルとURLです。
    \"\"\"
    pass


def get_weather(city: str) -> dict:
    \"\"\"
    指定した都市の現在の天気情報を取得します。

    Args:
        city (str): 天気を確認したい都市の名前。例： "東京"

    Returns:
        dict: 天気情報の辞書。{'temperature': 20.5, 'condition': 'Cloudy', 'humidity': 72}
    \"\"\"
    pass


def execute_script(script: str) -> str:
    \"\"\"
    ユーザから受け取ったスクリプトまたはコマンドを実行します。

    Args:
        script (str): 実行したいコマンドまたはスクリプト。例： "ls -la"

    Returns:
        str: 実行結果の出力またはエラーメッセージ。
    \"\"\"
    pass

"""
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/f46cb92d-322e-4b48-b034-a35b4aea0dec.png)
User「今日のAIニュースと埼玉の天気を教えて」
Assistant「`<tool_call>perform_web_search(query="AI ニュース"), get_weather(city="埼玉")</tool_call>`」

しっかりpython関数を2個同時に呼び出してくれました。
関数の実装は作るのが面倒なので、ここでは関数の戻り値にそれっぽいものを適当に入れてみます。

<details><summary>それっぽいもの</summary>

LLMだし戻り値適当でも大丈夫でしょ

```
chat.append({"role": "user", "content": """<tool_response>

生成AI・人工知能のニュース一覧
NHK
https://www3.nhk.or.jp › ... › 注目ワード一覧
ノーベル化学賞 AIでたんぱく質の構造予測に成功の研究者ら3人 · ノーベル物理学賞にAIの中核「機械学習」の基礎に関わった2人 · 進化するAI ゲーム開発に広がるか · スマホが ...

「AI」を日経が解説・最新ニュース一覧
日本経済新聞
https://www.nikkei.com › topics
AI · トヨタやマツダ、AIで車両開発 実車使わず期間短縮 · 米電力大手コンステレーション・エナジー、AIが株価翻弄 · NVIDIA・Walmart幹部が語る、小売りAI活用の先進事例.

AIニュース｜DXを推進するAIポータルメディア ...
AIsmiley
https://aismiley.co.jp › ai_news
このサイトでは毎日更新される最新のAI・人工知能ニュースから編集部が独自に取材して作成した記事と話題のAIサービスや人工知能の導入活用事例の記事をピックアップし ...
ニュースランキング
トピックス一覧
AIサービス
AIリスキリングカオスマップ 2025

人工知能（AI）の最新ニュース
朝日新聞
https://www.asahi.com › topics › word › 人工知能（Ａ...
朝日新聞の、人工知能（AI）に関するニュースや速報記事一覧です。人工知能（AI）に関する現状や背景を、わかりやすくお伝えします。,
検索結果：
晴時々曇
14
°C°F
降水確率: 10%
湿度: 42%
風速: 2 m/s
</tool_response>"""})
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/3a44cf2d-badd-4b07-b483-022fad211568.png)


User「今日のAIニュースと埼玉の天気を教えて」
Assistant「`<tool_call>perform_web_search(query="AI ニュース"), get_weather(city="埼玉")</tool_call>`」
User「`<tool_responce>それっぽいもの</tool_responce>`」
Assistant「こんにちは！今日はAIに関する興味深いニュースがいくつかありますので、ご紹介しますね。最近のAIに関するニュースとしては、以下のようなものがあります：

1. **ノーベル化学賞**: AIを活用してタンパク質の構造を予測する技術を開発した研究者らが受賞しました。この技術は、医薬品の開発や材料設計など、幅広い分野での応用が期待されています。

2. **AIと自動車産業**: トヨタ自動車やマツダなどの自動車メーカーが、AIを活用して車両の開発期間を短縮する取り組みを進めています。これにより、新しい車種の開発がより効率的に行えるようになるでしょう。

3. **AIによる株価予測**: アメリカの電力大手企業コンステレーション・エナジーがAIを使って株価の変動を予測する取り組みを行っています。AIによる予測が実際の株価の動きに影響を与える可能性があります。

また、埼玉県の天気ですが、今日は**晴時々曇**で、気温は**14℃**、降水確率は**10%**、湿度は**42%**となっています。比較的穏やかな一日になりそうですが、念のため天気予報を確認して、適切な服装でお過ごしください。

何か他に質問があれば、お気軽にどうぞ！」

しっかり戻り値で取得した情報を回答に反映していることがわかります。  

最近はSarashina2.2をはじめ小さいモデルでも性能が上がってきていてうれしいですね。