LangGraphの新しいライブラリー「langgraph-bigtool」の爆笑（？）デモ

新しいのがでたので、やってみました。

https://github.com/langchain-ai/langgraph-bigtool

説明文を和訳すると、

> langgraph-bigtool は、多数のツールにアクセスできる LangGraph エージェントを作成するための Python ライブラリです。LangGraph の長期メモリ ストアを活用して、エージェントが特定の問題に関連するツールを検索して取得できるようにします。

ピンときませんね。。

---

READMEを上からやっていきます。

`langgraph-bigtool`をインストールします。LLMとEmbeddingsも必要ですのでlangchain_awsも一緒にインストールします。

```shell
pip install langgraph-bigtool langchain_aws
```

ここからPythonのコードです。

まずはライブラリーをインポートします。

```python
import math
import types
import uuid

from langchain_aws import BedrockEmbeddings, ChatBedrock
from langgraph.store.memory import InMemoryStore

from langgraph_bigtool import create_agent
from langgraph_bigtool.utils import (
    convert_positional_only_function_to_tool
)
```

そして、言われるがまま、`all_tools`を生成します。

```python
# Collect functions from `math` built-in
all_tools = []
for function_name in dir(math):
    function = getattr(math, function_name)
    if not isinstance(function, types.BuiltinFunctionType):
        continue
    # This is an idiosyncrasy of the `math` library
    if tool := convert_positional_only_function_to_tool(function):
        all_tools.append(tool)
```

何をしてるかよくかからんので、all_toolsを出力してみますと、

```python
from pprint import pprint

pprint(all_tools)
```

```
[StructuredTool(name='acos', description='Return the arc cosine (measured in radians) of x.\n\nThe result is between 0 and pi.', args_schema=<class 'langchain_core.utils.pydantic.acos'>, func=<function acos at 0x7f8c68270040>),
 StructuredTool(name='acosh', description='Return the inverse hyperbolic cosine of x.', args_schema=<class 'langchain_core.utils.pydantic.acosh'>, func=<function acosh at 0x7f8c68270180>),
 StructuredTool(name='asin', description='Return the arc sine (measured in radians) of x.\n\nThe result is between -pi/2 and pi/2.', args_schema=<class 'langchain_core.utils.pydantic.asin'>, func=<function asin at 0x7f8c68270d60>),
 StructuredTool(name='asinh', description='Return the inverse hyperbolic sine of x.', args_schema=<class 'langchain_core.utils.pydantic.asinh'>, func=<function asinh at 0x7f8c68271800>),
 StructuredTool(name='atan', description='Return the arc tangent (measured in radians) of x.\n\nThe result is between -pi/2 and pi/2.', args_schema=<class 'langchain_core.utils.pydantic.atan'>, func=<function atan at 0x7f8c68272020>),
 StructuredTool(name='atan2', description='Return the arc tangent (measured in radians) of y/x.\n\nUnlike atan(y/x), the signs of both x and y are considered.', args_schema=<class 'langchain_core.utils.pydantic.atan2'>, func=<function atan2 at 0x7f8c68271080>),
 StructuredTool(name='atanh', description='Return the inverse hyperbolic tangent of x.', args_schema=<class 'langchain_core.utils.pydantic.atanh'>, func=<function atanh at 0x7f8c68270cc0>),
 StructuredTool(name='cbrt', description='Return the cube root of x.', args_schema=<class 'langchain_core.utils.pydantic.cbrt'>, func=<function cbrt at 0x7f8c68272fc0>),
 StructuredTool(name='ceil', description='Return the ceiling of x as an Integral.\n\nThis is the smallest integer >= x.', args_schema=<class 'langchain_core.utils.pydantic.ceil'>, func=<function ceil at 0x7f8c68271da0>),
 StructuredTool(name='comb', description='Number of ways to choose k items from n items without repetition and without order.\n\nEvaluates to n! / (k! * (n - k)!) when k <= n and evaluates\nto zero when k > n.\n\nAlso called the binomial coefficient because it is equivalent\nto the coefficient of k-th term in polynomial expansion of the\nexpression (1 + x)**n.\n\nRaises TypeError if either of the arguments are not integers.\nRaises ValueError if either of the arguments are negative.', args_schema=<class 'langchain_core.utils.pydantic.comb'>, func=<function comb at 0x7f8c682707c0>),
 StructuredTool(name='copysign', description='Return a float with the magnitude (absolute value) of x but the sign of y.\n\nOn platforms that support signed zeros, copysign(1.0, -0.0)\nreturns -1.0.', args_schema=<class 'langchain_core.utils.pydantic.copysign'>, func=<function copysign at 0x7f8c68272f20>),
 StructuredTool(name='cos', description='Return the cosine of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.cos'>, func=<function cos at 0x7f8c682711c0>),
 StructuredTool(name='cosh', description='Return the hyperbolic cosine of x.', args_schema=<class 'langchain_core.utils.pydantic.cosh'>, func=<function cosh at 0x7f8c68272840>),
 StructuredTool(name='degrees', description='Convert angle x from radians to degrees.', args_schema=<class 'langchain_core.utils.pydantic.degrees'>, func=<function degrees at 0x7f8c68272980>),
 StructuredTool(name='dist', description='Return the Euclidean distance between two points p and q.\n\nThe points should be specified as sequences (or iterables) of\ncoordinates.  Both inputs must have the same dimension.\n\nRoughly equivalent to:\n    sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))', args_schema=<class 'langchain_core.utils.pydantic.dist'>, func=<function dist at 0x7f8c68272ca0>),
 StructuredTool(name='erf', description='Error function at x.', args_schema=<class 'langchain_core.utils.pydantic.erf'>, func=<function erf at 0x7f8c682700e0>),
 StructuredTool(name='erfc', description='Complementary error function at x.', args_schema=<class 'langchain_core.utils.pydantic.erfc'>, func=<function erfc at 0x7f8c68270a40>),
 StructuredTool(name='exp', description='Return e raised to the power of x.', args_schema=<class 'langchain_core.utils.pydantic.exp'>, func=<function exp at 0x7f8c68272d40>),
 StructuredTool(name='exp2', description='Return 2 raised to the power of x.', args_schema=<class 'langchain_core.utils.pydantic.exp2'>, func=<function exp2 at 0x7f8c68273600>),
 StructuredTool(name='expm1', description='Return exp(x)-1.\n\nThis function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.', args_schema=<class 'langchain_core.utils.pydantic.expm1'>, func=<function expm1 at 0x7f8c682728e0>),
 StructuredTool(name='fabs', description='Return the absolute value of the float x.', args_schema=<class 'langchain_core.utils.pydantic.fabs'>, func=<function fabs at 0x7f8c68272520>),
 StructuredTool(name='factorial', description='Find n!.\n\nRaise a ValueError if x is negative or non-integral.', args_schema=<class 'langchain_core.utils.pydantic.factorial'>, func=<function factorial at 0x7f8c68272e80>),
 StructuredTool(name='floor', description='Return the floor of x as an Integral.\n\nThis is the largest integer <= x.', args_schema=<class 'langchain_core.utils.pydantic.floor'>, func=<function floor at 0x7f8c68271120>),
 StructuredTool(name='fmod', description='Return fmod(x, y), according to platform C.\n\nx % y may differ.', args_schema=<class 'langchain_core.utils.pydantic.fmod'>, func=<function fmod at 0x7f8c9010e2a0>),
 StructuredTool(name='frexp', description='Return the mantissa and exponent of x, as pair (m, e).\n\nm is a float and e is an int, such that x = m * 2.**e.\nIf x is 0, m and e are both 0.  Else 0.5 <= abs(m) < 1.0.', args_schema=<class 'langchain_core.utils.pydantic.frexp'>, func=<function frexp at 0x7f8c68273a60>),
 StructuredTool(name='fsum', description='Return an accurate floating point sum of values in the iterable seq.\n\nAssumes IEEE-754 floating point arithmetic.', args_schema=<class 'langchain_core.utils.pydantic.fsum'>, func=<function fsum at 0x7f8c682709a0>),
 StructuredTool(name='gamma', description='Gamma function at x.', args_schema=<class 'langchain_core.utils.pydantic.gamma'>, func=<function gamma at 0x7f8c68271c60>),
 StructuredTool(name='isclose', description='Determine whether two floating point numbers are close in value.\n\n  rel_tol\n    maximum difference for being considered "close", relative to the\n    magnitude of the input values\n  abs_tol\n    maximum difference for being considered "close", regardless of the\n    magnitude of the input values\n\nReturn True if a is close in value to b, and False otherwise.\n\nFor the values to be considered close, the difference between them\nmust be smaller than at least one of the tolerances.\n\n-inf, inf and NaN behave similarly to the IEEE 754 Standard.  That\nis, NaN is not close to anything, even itself.  inf and -inf are\nonly close to themselves.', args_schema=<class 'langchain_core.utils.pydantic.isclose'>, func=<function isclose at 0x7f8c68270ae0>),
 StructuredTool(name='isfinite', description='Return True if x is neither an infinity nor a NaN, and False otherwise.', args_schema=<class 'langchain_core.utils.pydantic.isfinite'>, func=<function isfinite at 0x7f8c682734c0>),
 StructuredTool(name='isinf', description='Return True if x is a positive or negative infinity, and False otherwise.', args_schema=<class 'langchain_core.utils.pydantic.isinf'>, func=<function isinf at 0x7f8c682720c0>),
 StructuredTool(name='isnan', description='Return True if x is a NaN (not a number), and False otherwise.', args_schema=<class 'langchain_core.utils.pydantic.isnan'>, func=<function isnan at 0x7f8c68272ac0>),
 StructuredTool(name='isqrt', description='Return the integer part of the square root of the input.', args_schema=<class 'langchain_core.utils.pydantic.isqrt'>, func=<function isqrt at 0x7f8c68271e40>),
 StructuredTool(name='ldexp', description='Return x * (2**i).\n\nThis is essentially the inverse of frexp().', args_schema=<class 'langchain_core.utils.pydantic.ldexp'>, func=<function ldexp at 0x7f8c68270f40>),
 StructuredTool(name='lgamma', description='Natural logarithm of absolute value of Gamma function at x.', args_schema=<class 'langchain_core.utils.pydantic.lgamma'>, func=<function lgamma at 0x7f8c68271620>),
 StructuredTool(name='log10', description='Return the base 10 logarithm of x.', args_schema=<class 'langchain_core.utils.pydantic.log10'>, func=<function log10 at 0x7f8c682704a0>),
 StructuredTool(name='log1p', description='Return the natural logarithm of 1+x (base e).\n\nThe result is computed in a way which is accurate for x near zero.', args_schema=<class 'langchain_core.utils.pydantic.log1p'>, func=<function log1p at 0x7f8c68271940>),
 StructuredTool(name='log2', description='Return the base 2 logarithm of x.', args_schema=<class 'langchain_core.utils.pydantic.log2'>, func=<function log2 at 0x7f8c68270680>),
 StructuredTool(name='modf', description='Return the fractional and integer parts of x.\n\nBoth results carry the sign of x and are floats.', args_schema=<class 'langchain_core.utils.pydantic.modf'>, func=<function modf at 0x7f8c68271b20>),
 StructuredTool(name='nextafter', description='Return the floating-point value the given number of steps after x towards y.\n\nIf steps is not specified or is None, it defaults to 1.\n\nRaises a TypeError, if x or y is not a double, or if steps is not an integer.\nRaises ValueError if steps is negative.', args_schema=<class 'langchain_core.utils.pydantic.nextafter'>, func=<function nextafter at 0x7f8c68273920>),
 StructuredTool(name='perm', description='Number of ways to choose k items from n items without repetition and with order.\n\nEvaluates to n! / (n - k)! when k <= n and evaluates\nto zero when k > n.\n\nIf k is not specified or is None, then k defaults to n\nand the function returns n!.\n\nRaises TypeError if either of the arguments are not integers.\nRaises ValueError if either of the arguments are negative.', args_schema=<class 'langchain_core.utils.pydantic.perm'>, func=<function perm at 0x7f8c682732e0>),
 StructuredTool(name='pow', description='Return x**y (x to the power of y).', args_schema=<class 'langchain_core.utils.pydantic.pow'>, func=<function pow at 0x7f8c68272de0>),
 StructuredTool(name='prod', description='Calculate the product of all the elements in the input iterable.\n\nThe default start value for the product is 1.\n\nWhen the iterable is empty, return the start value.  This function is\nintended specifically for use with numeric values and may reject\nnon-numeric types.', args_schema=<class 'langchain_core.utils.pydantic.prod'>, func=<function prod at 0x7f8c68271d00>),
 StructuredTool(name='radians', description='Convert angle x from degrees to radians.', args_schema=<class 'langchain_core.utils.pydantic.radians'>, func=<function radians at 0x7f8c90226e80>),
 StructuredTool(name='remainder', description='Difference between x and the closest integer multiple of y.\n\nReturn x - n*y where n*y is the closest integer multiple of y.\nIn the case where x is exactly halfway between two multiples of\ny, the nearest even value of n is used. The result is always exact.', args_schema=<class 'langchain_core.utils.pydantic.remainder'>, func=<function remainder at 0x7f8c68273420>),
 StructuredTool(name='sin', description='Return the sine of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.sin'>, func=<function sin at 0x7f8c682719e0>),
 StructuredTool(name='sinh', description='Return the hyperbolic sine of x.', args_schema=<class 'langchain_core.utils.pydantic.sinh'>, func=<function sinh at 0x7f8c68272700>),
 StructuredTool(name='sqrt', description='Return the square root of x.', args_schema=<class 'langchain_core.utils.pydantic.sqrt'>, func=<function sqrt at 0x7f8c68273740>),
 StructuredTool(name='sumprod', description='Return the sum of products of values from two iterables p and q.\n\nRoughly equivalent to:\n\n    sum(itertools.starmap(operator.mul, zip(p, q, strict=True)))\n\nFor float and mixed int/float inputs, the intermediate products\nand sums are computed with extended precision.', args_schema=<class 'langchain_core.utils.pydantic.sumprod'>, func=<function sumprod at 0x7f8c68272b60>),
 StructuredTool(name='tan', description='Return the tangent of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.tan'>, func=<function tan at 0x7f8c68271580>),
 StructuredTool(name='tanh', description='Return the hyperbolic tangent of x.', args_schema=<class 'langchain_core.utils.pydantic.tanh'>, func=<function tanh at 0x7f8c68271bc0>),
 StructuredTool(name='trunc', description='Truncates the Real x to the nearest Integral toward 0.\n\nUses the __trunc__ magic method.', args_schema=<class 'langchain_core.utils.pydantic.trunc'>, func=<function trunc at 0x7f8c68272340>),
 StructuredTool(name='ulp', description='Return the value of the least significant bit of the float x.', args_schema=<class 'langchain_core.utils.pydantic.ulp'>, func=<function ulp at 0x7f8c68271ee0>)]
```

**！！！**

めっちゃツールが生成されてますwww

:::note
どうやら、`math`モジュールにある関数をツール化しているようです！
:::

ツールの数は、

```python
print(len(all_tools))
```
```
52
```

52個www

---

ということで、大量のツール（bogtool）をいい感じに扱うライブラリーが、この「langgraph-bigtool」ということのようです。


さて、この大量のツールをどうするのでしょうか？

引き続きREADMEの内容をやっていきましょう。

ツールをディクショナリーにします。

```python
tool_registry = {
    str(uuid.uuid4()): tool
    for tool in all_tools
}
```

InMemoryStoreを用意します。

```python
embeddings = BedrockEmbeddings(
    model_id="amazon.titan-embed-text-v2:0", region_name="us-east-1"
)

store = InMemoryStore(
    index={
        "embed": embeddings,
        "dims": 1024,
        "fields": ["description"],
    }
)
```

そして、先程の大量のツールをこのstoreに登録します。

```python
for tool_id, tool in tool_registry.items():
    store.put(
        ("tools",),
        tool_id,
        {
            "description": f"{tool.name}: {tool.description}",
        },
    )
```

ここまでで下準備が完了です。

ReActエージェントを作成します。

```python
llm = ChatBedrock(model="us.amazon.nova-pro-v1:0", region="us-east-1")

builder = create_agent(llm, tool_registry)
agent = builder.compile(store=store)
```

こんな感じのエージェントになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/45a323bc-6f30-49f4-ad5f-aa4d92ee3185.png)

実行してみましょう

```python
query = "Use available tools to calculate arc cosine of 0.5."

for step in agent.stream(
    {"messages": query},
    stream_mode="updates",
):
    for _, update in step.items():
        for message in update.get("messages", []):
            message.pretty_print()
```

実行結果はこちら。

```
==================================[1m Ai Message [0m==================================

[{'type': 'text', 'text': '<thinking> To calculate the arc cosine of 0.5, I need to find a tool that can perform trigonometric calculations. I will search for a tool that can calculate the arc cosine. </thinking>\n'}, {'type': 'tool_use', 'name': 'retrieve_tools', 'input': {'query': 'calculate arc cosine'}, 'id': 'tooluse_ub_OudHASu68UFCLe4ZiXQ'}]
Tool Calls:
  retrieve_tools (tooluse_ub_OudHASu68UFCLe4ZiXQ)
 Call ID: tooluse_ub_OudHASu68UFCLe4ZiXQ
  Args:
    query: calculate arc cosine
=================================[1m Tool Message [0m=================================

Available tools: ['acos', 'acosh']
==================================[1m Ai Message [0m==================================

[{'type': 'text', 'text': "<thinking> The available tools include 'acos', which can calculate the arc cosine. I will use this tool to calculate the arc cosine of 0.5. </thinking> "}, {'type': 'tool_use', 'name': 'acos', 'input': {'x': 0.5}, 'id': 'tooluse_XtSWmOXwRh2SrBIe9I3zyQ'}]
Tool Calls:
  acos (tooluse_XtSWmOXwRh2SrBIe9I3zyQ)
 Call ID: tooluse_XtSWmOXwRh2SrBIe9I3zyQ
  Args:
    x: 0.5
=================================[1m Tool Message [0m=================================
Name: acos

1.0471975511965979
==================================[1m Ai Message [0m==================================

The arc cosine of 0.5 is approximately 1.047 radians.
```

ログから想像するに、以下のような動作になっていると思います。

1. 使いたいツールのクエリーを生成AIに作らせる
1. クエリーを使って大量のツール（bogtool）からベクトル検索をして、候補のツールを返却する
1. 候補のツールから、使用するツールを生成AIが判断する
1. ツールを実行する
1. 生成AIがツールの結果を下に回答を生成する

:::note
なにこれ、賢い！
:::

ツールを50個用意できるシーンが想像できないですが、欲しい人にはほしいライブラリーなのではないでしょうか！

---

langgraph-bigtoolには、ツール取得処理のカスタマイズする機能が用意されていまして、ベクトル検索を使わない方法も紹介されています。