Hugging Faceが作ったAIエージェントフレームワーク「smolagents」

Hugging FaceがAIエージェントフレームワークを公開してました！

https://huggingface.co/docs/smolagents/index

:::note
大晦日リリースということで、さすが見に過ごしてました
:::

まずですね、アイコンが可愛いです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/657c1c1d-5516-472d-7ae6-cda5bc745ec4.png)

可愛すぎ～～

---

ローンチブログはこちら

https://huggingface.co/blog/smolagents

:::note
ローンチブログでは、「AIエージェントとはなにか？」「AIエージェントを使うべきとき/避けるべきとき」といった、ベーシックだけど、とてもためになる話が載ってますので、ぜひ一読ください。
:::

## smolagentsの特徴

smolagentsの特徴はズバリ **「Cade Agent」** です。

LLMに外部ツールを使わせる、いわゆるFunction Callingのアプローチでは、JSONを介してツール名やパラメーターをLLMに渡しますが、smolagentsでは、**Pythonコードを渡す**という動作になります。

いくつかの論文で、JSONではなくプログラムコードを渡すほうが精度が良いという結果になっているようです。

ドキュメントより画像を拝借
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d2f94320-10f2-417c-9ccb-8ed710ea14a9.png)

左がJSONを渡す形のFunction Callingです。複数ターンでやっと回答に辿り着くということが説明されています。
右がプログラムコードを渡す形で、この場合は、1ターンで回答にたどりつけます。



:::note
ブログでは他に以下の特徴も触れられています。

- **シンプル**：smolagentsの実装コードは1,000行ちょっと。とても軽量です
- **Hugging Faceとの統合**：構築したAIエージェントをSpacesとしてHugging Faceにアップロード可能です
- **様々なLLMに対応**：OpenAIのAPIやHugging FaceでホストされているAPIはもちろん、LiteLLMに対応しているので、100以上のLLMに対応しています
:::

## エージェントを作ってみる

smolagentsをインストールします。

```shell
pip install smolagents
```

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, LiteLLMModel

model = LiteLLMModel(
    model_id="bedrock/amazon.nova-pro-v1:0", aws_region_name="us-east-1"
)
agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=model)

agent.run("東京から大阪に時速10kmで進むと、何日後に到着しますか？直進することとします。")
```

コードはかなり短いですね。

少しだけ解説します。

- model

    モデルは、`TransformersModel`、`HfApiModel`、`LiteLLMModel`、`AzureOpenAIServerModel`、`MLXModel`が用意されています。
    一見少なそうですが、LiteLLMModelは内部でLiteLLMが使われており、GeminiやBedrockなど多数のモデルを使用できます。
- tools

    エージェントが使用できるツールを定義します。smolagentsにビルトインのツールとして、「DuckDuckGo web search」「Python code interpreter」「Transcriber」が用意されています。独自のツールを定義することも可能です。

---

実行して思考の様子を確認します。

:::note
以下、コンソールに出力されたログです
:::

* 思考と実行の様子

    ```
    ╭──────────────────────────────────────────────────── New run ────────────────────────────────────────────────────╮
    │                                                                                                                 │
    │ 東京から大阪に時速10kmで進むと、何日後に到着しますか？直進することとします。                                    │
    │                                                                                                                 │
    ╰─ LiteLLMModel - bedrock/amazon.nova-pro-v1:0 ───────────────────────────────────────────────────────────────────╯
    ```

    ステップ１が始まります。

    ```
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ```

    Pythonのコードを生成します。

    ```
     ─ Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────── 
      import math                                                                                                      
                                                                                                                       
      # Step 1: Find the distance between Tokyo and Osaka                                                              
      distance_query = "distance between Tokyo and Osaka"                                                              
      distance_result = web_search(query=distance_query)                                                               
      print("Distance result:", distance_result)                                                                       
                                                                                                                       
      # Step 2: Calculate the time it takes to travel the distance at 10 km/h                                          
      speed = 10  # km/h                                                                                               
      time_hours = None  # will be calculated after getting the distance                                               
                                                                                                                       
      if "km" in distance_result:                                                                                      
          distance_km = float(distance_result.split()[0])                                                              
          time_hours = distance_km / speed                                                                             
                                                                                                                       
      # Step 3: Convert the time from hours to days                                                                    
      time_days = time_hours / 24 if time_hours is not None else None                                                  
      print("Time in days:", time_days)                                                                                
     ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── 
    ```

    生成したPythonコードを実行しますがエラーとなります。

    ```
    Code execution failed at line 'if "km" in distance_result:
        distance_km = float(distance_result.split()[0])
        time_hours = distance_km / speed' due to: ValueError: could not convert string to float: '##'
    ```

    エラーになるとステップが終了します。
    ステップ２が始まります

    ```
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ```

    先ほどとは別のPythonコードが生成されます。
    `distance_result`変数の値はステップ１での実行で取得した値と思われます。新しく実行されるわけではなく、先程の続きで実行されるようです。

    ```
     ─ Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────── 
  import re                                                                                                        
                                                                                                                   
  # Extract the distance from the HTML content                                                                     
  distance_match = re.search(r'The distance between Tokyo and Osaka is (\d+) km.', distance_result)                
  if distance_match:                                                                                               
      distance_km = float(distance_match.group(1))                                                                 
      time_hours = distance_km / speed                                                                             
      time_days = time_hours / 24                                                                                  
      print("Time in days:", time_days)                                                                            
  else:                                                                                                            
      print("Distance not found in the search results.")                                                           
     ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── 
    ```

    ステップ２の結果が出力されます。

    ```
    Execution logs:
    Time in days: 2.1458333333333335
    
    Out: 2.1458333333333335
    ```

    ここで終わりそうですが、ステップ３が始まります。
    次に生成されるコードは以下のような簡単なものでした。
    どうやら、`final_answer`関数の結果が、最終回塔になるようです。

    ```
     ─ Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────── 
      final_answer(2.15)                                                                                               
     ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── 
    ```
    
    そして、最終的に回答「2.15」が出力されました。

## まとめ

smolagentsを軽く触ってみました。
アプローチは面白うそうだと感じましたが、生成するコードが結構実行時エラーになるので、体当たりしてる感がありますねw