LangChain v0.3　その16　~イケテルぜ！ langgraph-bigtool~

# 1. はじめに

https://qiita.com/moritalous/items/f17c154df8c936e46875

いや、やられた。
だよね。既存の便利なライブラリ（ここでは`math`）を最大限活用するって思いつけなかったわ。
さすがです。
そして、この記事読んで爆笑しました。これは便利。
なので僕も手を動かしてみた。（一部コピペさせていただきました。@moritalousさん、ありがとうございます。）

**サクサクやってくぜ**

<br><br>

# 2. 環境
:::note
バージョン情報
Python==3.11.9
langchain==0.3.19
python-dotenv
langchain-openai==0.3.7
langgraph==0.3.2
langchain-core==0.3.40
langchain-community==0.3.18
langgraph-bigtool==0.0.2   <- new
:::

<br><br>

# 3. プログラム
## 3-1. ライブラリインポート
```python
import math
import os
import types
from pprint import pprint
from typing import Any, Dict
from dotenv import load_dotenv
# モデル
from langchain_openai import AzureChatOpenAI
from langchain_openai import AzureOpenAIEmbeddings

# tools関連
from langchain_core.tools import tool
# graph関連
from langgraph.store.memory import InMemoryStore

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

<br>

## 3-2. 環境関連
```python
load_dotenv('.env')

END_POINT = os.getenv('END_POINT')
API_VERSION = os.getenv('API_VERSION')
API_KEY = os.getenv('API_KEY')
```

<br>

## 3-3. モデルの用意（チャットモデル、埋め込みモデル）
```python
model = AzureChatOpenAI(
    azure_deployment='gpt-4o-mini',
    azure_endpoint=END_POINT,
    api_version=API_VERSION,
    api_key=API_KEY
)
```

<br>

## 3-4. toolsの用意

```python
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)
# ****.py:8: LangChainBetaWarning: The function `convert_positional_only_function_to_tool` is in beta. It is actively being worked on, so the API may change.
#  if tool := convert_positional_only_function_to_tool(function):
```
まだ、`convert_positional_only_function_to_tool`はベータ版だよ。まだ作ってるから変わるよ。だって！
正式版が楽しみです。
<br>


## 3-5. toolsの確認
```python
from pprint import pprint

pprint(all_tools[:5])
pprint(len(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 0x0000021FD6448040>),
#  StructuredTool(name='acosh', description='Return the inverse hyperbolic cosine of x.', args_schema=<class 'langchain_core.utils.pydantic.acosh'>, func=<function acosh at 0x0000021FD6448220>),
#  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 0x0000021FD64491C0>),
#  StructuredTool(name='asinh', description='Return the inverse hyperbolic sine of x.', args_schema=<class 'langchain_core.utils.pydantic.asinh'>, func=<function asinh at 0x0000021FD644A160>),
#  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 0x0000021FD64494E0>)]
# 51
```


<br>

```python
tools_by_name = {
    tool.name: tool
    for tool in all_tools
}
print(tools_by_name)
```

<details><summary>長いので出力はこちら</summary>

```text
{'acos': 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 0x000001EF861EC900>),
 'acosh': StructuredTool(name='acosh', description='Return the inverse hyperbolic cosine of x.', args_schema=<class 'langchain_core.utils.pydantic.acosh'>, func=<function acosh at 0x000001EF861EC860>),
 'asin': 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 0x000001EF861EDBC0>),
 'asinh': StructuredTool(name='asinh', description='Return the inverse hyperbolic sine of x.', args_schema=<class 'langchain_core.utils.pydantic.asinh'>, func=<function asinh at 0x000001EF861EE980>),
 'atan': 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 0x000001EF861EF740>),
 'atan2': 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 0x000001EF861ED440>),
 'atanh': StructuredTool(name='atanh', description='Return the inverse hyperbolic tangent of x.', args_schema=<class 'langchain_core.utils.pydantic.atanh'>, func=<function atanh at 0x000001EF861EE200>),
 'cbrt': StructuredTool(name='cbrt', description='Return the cube root of x.', args_schema=<class 'langchain_core.utils.pydantic.cbrt'>, func=<function cbrt at 0x000001EF861EDDA0>),
 'ceil': 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 0x000001EF861ED620>),
 'comb': 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 0x000001EF861ECC20>),
 'copysign': 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 0x000001EF861EDC60>),
 'cos': StructuredTool(name='cos', description='Return the cosine of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.cos'>, func=<function cos at 0x000001EF861EDF80>),
 'cosh': StructuredTool(name='cosh', description='Return the hyperbolic cosine of x.', args_schema=<class 'langchain_core.utils.pydantic.cosh'>, func=<function cosh at 0x000001EF861ECCC0>),
 'degrees': StructuredTool(name='degrees', description='Convert angle x from radians to degrees.', args_schema=<class 'langchain_core.utils.pydantic.degrees'>, func=<function degrees at 0x000001EF861EEA20>),
 'dist': 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 0x000001EF861EE340>),
 'erf': StructuredTool(name='erf', description='Error function at x.', args_schema=<class 'langchain_core.utils.pydantic.erf'>, func=<function erf at 0x000001EF861EEAC0>),
 'erfc': StructuredTool(name='erfc', description='Complementary error function at x.', args_schema=<class 'langchain_core.utils.pydantic.erfc'>, func=<function erfc at 0x000001EF861EE020>),
 'exp': StructuredTool(name='exp', description='Return e raised to the power of x.', args_schema=<class 'langchain_core.utils.pydantic.exp'>, func=<function exp at 0x000001EF861EDA80>),
 'exp2': StructuredTool(name='exp2', description='Return 2 raised to the power of x.', args_schema=<class 'langchain_core.utils.pydantic.exp2'>, func=<function exp2 at 0x000001EF861EF060>),
 'expm1': 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 0x000001EF861EEDE0>),
 'fabs': StructuredTool(name='fabs', description='Return the absolute value of the float x.', args_schema=<class 'langchain_core.utils.pydantic.fabs'>, func=<function fabs at 0x000001EF861ECAE0>),
 'factorial': 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 0x000001EF861EE480>),
 'floor': 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 0x000001EF861EE160>),
 'fmod': 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 0x000001EF861EEF20>),
 'frexp': 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 0x000001EF861EFE20>),
 'fsum': 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 0x000001EF861EFBA0>),
 'gamma': StructuredTool(name='gamma', description='Gamma function at x.', args_schema=<class 'langchain_core.utils.pydantic.gamma'>, func=<function gamma at 0x000001EF861EEB60>),
 'isclose': 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 0x000001EF861ED300>),
 'isfinite': 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 0x000001EF861EDD00>),
 'isinf': 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 0x000001EF861EF420>),
 'isnan': 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 0x000001EF861EE2A0>),
 'isqrt': 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 0x000001EF861ED8A0>),
 'ldexp': 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 0x000001EF861EF6A0>),
 'lgamma': 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 0x000001EF861EE8E0>),
 'log10': StructuredTool(name='log10', description='Return the base 10 logarithm of x.', args_schema=<class 'langchain_core.utils.pydantic.log10'>, func=<function log10 at 0x000001EF861EFEC0>),
 'log1p': 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 0x000001EF861EF920>),
 'log2': StructuredTool(name='log2', description='Return the base 2 logarithm of x.', args_schema=<class 'langchain_core.utils.pydantic.log2'>, func=<function log2 at 0x000001EF861EF7E0>),
 'modf': 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 0x000001EF861ED800>),
 'nextafter': StructuredTool(name='nextafter', description='Return the next floating-point value after x towards y.', args_schema=<class 'langchain_core.utils.pydantic.nextafter'>, func=<function nextafter at 0x000001EF86253CE0>),
 'perm': 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 0x000001EF861EF600>),
 'pow': 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 0x000001EF86251EE0>),
 'prod': 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 0x000001EF86253EC0>),
 'radians': StructuredTool(name='radians', description='Convert angle x from degrees to radians.', args_schema=<class 'langchain_core.utils.pydantic.radians'>, func=<function radians at 0x000001EF86253920>),
 'remainder': 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 0x000001EF86252520>),
 'sin': StructuredTool(name='sin', description='Return the sine of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.sin'>, func=<function sin at 0x000001EF862504A0>),
 'sinh': StructuredTool(name='sinh', description='Return the hyperbolic sine of x.', args_schema=<class 'langchain_core.utils.pydantic.sinh'>, func=<function sinh at 0x000001EF86253100>),
 'sqrt': StructuredTool(name='sqrt', description='Return the square root of x.', args_schema=<class 'langchain_core.utils.pydantic.sqrt'>, func=<function sqrt at 0x000001EF862522A0>),
 'tan': StructuredTool(name='tan', description='Return the tangent of x (measured in radians).', args_schema=<class 'langchain_core.utils.pydantic.tan'>, func=<function tan at 0x000001EF86253BA0>),
 'tanh': StructuredTool(name='tanh', description='Return the hyperbolic tangent of x.', args_schema=<class 'langchain_core.utils.pydantic.tanh'>, func=<function tanh at 0x000001EF86251760>),
 'trunc': 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 0x000001EF862527A0>),
 'ulp': 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 0x000001EF86250360>)}
```
</details>

<br>


## 3-6. toolsを実行する関数
```python
def execute_tool(tools_by_name: Dict[str, Any], tool_call: Dict[str, Any]) -> str:
    """Execute a tool call and return the result"""
    tool_name = tool_call["name"]

    if tool_name not in tools_by_name:
        return f"Error: Tool {tool_name} not found"
    else:
        # print(tool_call)
        pass

    tool = tools_by_name[tool_name]
    # print(tool)
    try:
        result = tool.invoke(tool_call["args"])
        return str(result)
    except Exception as e:
        return f"Error executing {tool_name}: {str(e)}"
```

<br>

## 3-7. バインドモデルの用意
```python
llm_with_tools = model.bind_tools(all_tools)
```


<br>


## 3-8. 簡易なAgent関数
```python
def agent_run(messages: list)-> list:
    while True:
        # モデルに入れて推論して、出力をmessagesに追加する
        response = llm_with_tools.invoke(messages)
        tool_calls = response.tool_calls
        tool_calls_len = len(tool_calls)

        messages.append(
            {"role": "assistant", "content": response.content, "tool_calls": tool_calls}
        )
        
        # tool_callingされない時の処理はモデルの出力のmessagesを返す
        if not tool_calls:
            # No more tools to call, return the final response
            print(f"{response.content=}")
            return messages

        # tool_callingされたときにtoolsを実行してmessagesに追加し、返す
        for i, tool_call in enumerate(tool_calls):
            print(tool_call)
            tool_result = execute_tool(tools_by_name, tool_call)
            
            messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call["id"],
                    "content": tool_result,
            })

```
<br>

# 4. 実行 
## 4-1. 実行
```python
user_input = "コサインの値を求めたいです。30度のコサイン"
messages = [{"role": "user", "content": user_input}]
messages = agent_run(messages)
pprint(messages)

# {'name': 'cos', 'args': {'x': 0.5235987755982988}, 'id': 'call_UXvSoQXimoTfQF3iVCTbbMr0', 'type': 'tool_call'}
# [{'content': 'コサインの値を求めたいです。30度のコサイン', 'role': 'user'},
#  {'name': 'cos', 'args': {'x': 0.5235987755982988}, 'id': 'call_mRWNkqC5llNzyZWXtUNVnLMd', 'type': 'tool_call'}
# response.content='30度のコサインの値は約0.866です。'
# [{'content': 'コサインの値を求めたいです。30度のコサイン', 'role': 'user'},
#  {'content': '',
#   'role': 'assistant',
#   'tool_calls': [{'args': {'x': 0.5235987755982988},
#                   'id': 'call_mRWNkqC5llNzyZWXtUNVnLMd',
#                   'name': 'cos',
#                   'type': 'tool_call'}]},
#  {'content': '0.8660254037844387',
#   'role': 'tool',
#   'tool_call_id': 'call_mRWNkqC5llNzyZWXtUNVnLMd'},
#  {'content': '30度のコサインの値は約0.866です。', 'role': 'assistant', 'tool_calls': []}]

```
おお！
でも、30度をラジアンに変換する計算はしないのね・・・ｗ


<br>



## 4-2. 少し難しめの実行
```python
user_input = """テストの結果が以下の通りになりました。標準偏差と全員の偏差値を計算してください。

# テスト結果
A氏 90点
B氏 70点
C氏 40点
D氏 65点
"""
messages = [{"role": "user", "content": user_input}]
messages = agent_run(messages)
pprint(messages)

# ### 結果
# **標準偏差**: 約17.9
#  **偏差値**:
#  - A氏: 約60.34
#  - B氏: 約52.22
#  - C氏: 約40.66
#  - D氏: 約49.01
```

出力は抜粋しました。長いで以下から見てください。



<details><summary>詳細出力</summary>

```text
{'name': 'fsum', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_rh1fvi2pzdJXi2LfojNlPg6R', 'type': 'tool_call'}
{'name': 'fsum', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_D62yvWi6pxbbKWKAGFC5FeMr', 'type': 'tool_call'}
{'name': 'pow', 'args': {'x': 4, 'y': 2}, 'id': 'call_Q64Kfi474F0rrshMFdgJ8WC7', 'type': 'tool_call'}
{'name': 'sqrt', 'args': {'x': 4}, 'id': 'call_hRspm4ghj83ynWaLnPp1Ov5p', 'type': 'tool_call'}
{'name': 'mean', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_UYxmJ8fQZno06j4AUfU18ayc', 'type': 'tool_call'}
{'name': 'std', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_N337IvTI7JpqjfYdXmOlOFYv', 'type': 'tool_call'}
{'name': 'sqrt', 'args': {'x': 16}, 'id': 'call_lrLo1vayFOzdmCDqHxsecLT2', 'type': 'tool_call'}
{'name': 'fsum', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_Dx3dvwc7M7W43UHrkHBGrGLv', 'type': 'tool_call'}
{'name': 'fsum', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_MdLzKj6WlRN7mSJCKrz1DW78', 'type': 'tool_call'}
{'name': 'pow', 'args': {'x': 2, 'y': 16}, 'id': 'call_slYo59fCvswNx3692jpr24Sn', 'type': 'tool_call'}
{'name': 'sqrt', 'args': {'x': 16}, 'id': 'call_NQ55zWOVIXVzAOQkw1qq6KeP', 'type': 'tool_call'}
{'name': 'fsum', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_cRfUkYoARYSjAS6EOtpStHhy', 'type': 'tool_call'}
{'name': 'comb', 'args': {'n': 4, 'k': 2}, 'id': 'call_os3bHjGM5V7IasIq5RE5bSSd', 'type': 'tool_call'}
{'name': 'sqrt', 'args': {'x': 4.0}, 'id': 'call_k8DAdWwie9sieffdADmShANI', 'type': 'tool_call'}
{'name': 'mean', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_yhHNZoIB205P1X9Z4Lf8IEnn', 'type': 'tool_call'}
{'name': 'prod', 'args': {'iterable': [90, 70, 40, 65]}, 'id': 'call_L9DZh5CfGZJGhhOjHwuX3xjf', 'type': 'tool_call'}
{'name': 'dist', 'args': {'p': [90, 70, 40, 65], 'q': [65, 40, 70, 90]}, 'id': 'call_w2tTRhaYSzOu73khxeDLiOuT', 'type': 'tool_call'}
{'name': 'sqrt', 'args': {'x': 1040}, 'id': 'call_CCV3B1AQfrKyW3a2IR0k96zj', 'type': 'tool_call'}
{'name': 'std', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_ORtkqey0ceYDkaGTrL81PZ71', 'type': 'tool_call'}
{'name': 'mean', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_zHphHLzaqTZJPHPkrQkqyOuD', 'type': 'tool_call'}
{'name': 'fmean', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_CCV3B1AQfrKyW3a2IR0k96zj', 'type': 'tool_call'}
{'name': 'fmean', 'args': {'seq': [90, 70, 40, 65]}, 'id': 'call_MCmiKJuVhXBWtRV4JzVOmFPb', 'type': 'tool_call'}
response.content='テスト結果から標準偏差と偏差値を計算します。\n\nまず、各値の合計を計算します。\n\n- A氏: 90点\n- B氏: 70点\n- C氏: 40点\n- D氏: 65点\n\n合計点 = 90 + 70 + 40 + 65 = 265\n\n次に、平均点を計算します。\n平均点 = 合計点 / 人数 = 265 / 4 = 66.25\n\n次に、各人のスコアから平均を引き、その結果を二乗します。\n- A氏: (90 - 66.25)² = 594.0625\n- B氏: (70 - 66.25)² = 14.0625\n- C氏: (40 - 66.25)² = 676.5625\n- D氏: (65 - 66.25)² = 1.5625\n\nこれらを合計します。\n594.0625 + 14.0625 + 676.5625 + 1.5625 = 1286.25\n\n次に、分散を計算します。\n分散 = 合計 / 人数 = 1286.25 / 4 = 321.5625\n\n最後に、標準偏差は分散の平方根です。\n標準偏差 = √321.5625 ≈ 17.9\n\n次に、偏差値を計算します。偏差値 = (得点 - 平均点) / 標準偏差 × 10 + 50\n\n- A氏: (90 - 66.25) / 17.9 × 10 + 50 ≈ 85.0\n- B氏: (70 - 66.25) / 17.9 × 10 + 50 ≈ 54.7\n- C氏: (40 - 66.25) / 17.9 × 10 + 50 ≈ 27.3\n- D氏: (65 - 66.25) / 17.9 × 10 + 50 ≈ 48.6\n\n**結果:**\n- 標準偏差: 約 17.9\n- A氏の偏差値: 約 85.0\n- B氏の偏差値: 約 54.7\n- C氏の偏差値: 約 27.3\n- D氏の偏差値: 約 48.6'
[{'content': 'テストの結果が以下の通りになりました。標準偏差と全員の偏差値を計算してください。\n'
             '\n'
             '# テスト結果\n'
             'A氏 90点\n'
             'B氏 70点\n'
             'C氏 40点\n'
             'D氏 65点\n',
  'role': 'user'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_rh1fvi2pzdJXi2LfojNlPg6R',
                  'name': 'fsum',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_D62yvWi6pxbbKWKAGFC5FeMr',
                  'name': 'fsum',
                  'type': 'tool_call'}]},
 {'content': '265.0',
  'role': 'tool',
  'tool_call_id': 'call_rh1fvi2pzdJXi2LfojNlPg6R'},
 {'content': '265.0',
  'role': 'tool',
  'tool_call_id': 'call_D62yvWi6pxbbKWKAGFC5FeMr'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'x': 4, 'y': 2},
                  'id': 'call_Q64Kfi474F0rrshMFdgJ8WC7',
                  'name': 'pow',
                  'type': 'tool_call'},
                 {'args': {'x': 4},
                  'id': 'call_hRspm4ghj83ynWaLnPp1Ov5p',
                  'name': 'sqrt',
                  'type': 'tool_call'}]},
 {'content': '16.0',
  'role': 'tool',
  'tool_call_id': 'call_Q64Kfi474F0rrshMFdgJ8WC7'},
 {'content': '2.0',
  'role': 'tool',
  'tool_call_id': 'call_hRspm4ghj83ynWaLnPp1Ov5p'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_UYxmJ8fQZno06j4AUfU18ayc',
                  'name': 'mean',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_N337IvTI7JpqjfYdXmOlOFYv',
                  'name': 'std',
                  'type': 'tool_call'}]},
 {'content': 'Error: Tool mean not found',
  'role': 'tool',
  'tool_call_id': 'call_UYxmJ8fQZno06j4AUfU18ayc'},
 {'content': 'Error: Tool std not found',
  'role': 'tool',
  'tool_call_id': 'call_N337IvTI7JpqjfYdXmOlOFYv'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'x': 16},
                  'id': 'call_lrLo1vayFOzdmCDqHxsecLT2',
                  'name': 'sqrt',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_Dx3dvwc7M7W43UHrkHBGrGLv',
                  'name': 'fsum',
                  'type': 'tool_call'}]},
 {'content': '4.0',
  'role': 'tool',
  'tool_call_id': 'call_lrLo1vayFOzdmCDqHxsecLT2'},
 {'content': '265.0',
  'role': 'tool',
  'tool_call_id': 'call_Dx3dvwc7M7W43UHrkHBGrGLv'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_MdLzKj6WlRN7mSJCKrz1DW78',
                  'name': 'fsum',
                  'type': 'tool_call'},
                 {'args': {'x': 2, 'y': 16},
                  'id': 'call_slYo59fCvswNx3692jpr24Sn',
                  'name': 'pow',
                  'type': 'tool_call'}]},
 {'content': '265.0',
  'role': 'tool',
  'tool_call_id': 'call_MdLzKj6WlRN7mSJCKrz1DW78'},
 {'content': '65536.0',
  'role': 'tool',
  'tool_call_id': 'call_slYo59fCvswNx3692jpr24Sn'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'x': 16},
                  'id': 'call_NQ55zWOVIXVzAOQkw1qq6KeP',
                  'name': 'sqrt',
                  'type': 'tool_call'}]},
 {'content': '4.0',
  'role': 'tool',
  'tool_call_id': 'call_NQ55zWOVIXVzAOQkw1qq6KeP'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_cRfUkYoARYSjAS6EOtpStHhy',
                  'name': 'fsum',
                  'type': 'tool_call'},
                 {'args': {'k': 2, 'n': 4},
                  'id': 'call_os3bHjGM5V7IasIq5RE5bSSd',
                  'name': 'comb',
                  'type': 'tool_call'}]},
 {'content': '265.0',
  'role': 'tool',
  'tool_call_id': 'call_cRfUkYoARYSjAS6EOtpStHhy'},
 {'content': '6',
  'role': 'tool',
  'tool_call_id': 'call_os3bHjGM5V7IasIq5RE5bSSd'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'x': 4.0},
                  'id': 'call_k8DAdWwie9sieffdADmShANI',
                  'name': 'sqrt',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_yhHNZoIB205P1X9Z4Lf8IEnn',
                  'name': 'mean',
                  'type': 'tool_call'}]},
 {'content': '2.0',
  'role': 'tool',
  'tool_call_id': 'call_k8DAdWwie9sieffdADmShANI'},
 {'content': 'Error: Tool mean not found',
  'role': 'tool',
  'tool_call_id': 'call_yhHNZoIB205P1X9Z4Lf8IEnn'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'iterable': [90, 70, 40, 65]},
                  'id': 'call_L9DZh5CfGZJGhhOjHwuX3xjf',
                  'name': 'prod',
                  'type': 'tool_call'},
                 {'args': {'p': [90, 70, 40, 65], 'q': [65, 40, 70, 90]},
                  'id': 'call_w2tTRhaYSzOu73khxeDLiOuT',
                  'name': 'dist',
                  'type': 'tool_call'}]},
 {'content': '16380000',
  'role': 'tool',
  'tool_call_id': 'call_L9DZh5CfGZJGhhOjHwuX3xjf'},
 {'content': '55.226805085936306',
  'role': 'tool',
  'tool_call_id': 'call_w2tTRhaYSzOu73khxeDLiOuT'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'x': 1040},
                  'id': 'call_CCV3B1AQfrKyW3a2IR0k96zj',
                  'name': 'sqrt',
                  'type': 'tool_call'}]},
 {'content': '32.2490309931942',
  'role': 'tool',
  'tool_call_id': 'call_CCV3B1AQfrKyW3a2IR0k96zj'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_ORtkqey0ceYDkaGTrL81PZ71',
                  'name': 'std',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_zHphHLzaqTZJPHPkrQkqyOuD',
                  'name': 'mean',
                  'type': 'tool_call'}]},
 {'content': 'Error: Tool std not found',
  'role': 'tool',
  'tool_call_id': 'call_ORtkqey0ceYDkaGTrL81PZ71'},
 {'content': 'Error: Tool mean not found',
  'role': 'tool',
  'tool_call_id': 'call_zHphHLzaqTZJPHPkrQkqyOuD'},
 {'content': '',
  'role': 'assistant',
  'tool_calls': [{'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_CCV3B1AQfrKyW3a2IR0k96zj',
                  'name': 'fmean',
                  'type': 'tool_call'},
                 {'args': {'seq': [90, 70, 40, 65]},
                  'id': 'call_MCmiKJuVhXBWtRV4JzVOmFPb',
                  'name': 'fmean',
                  'type': 'tool_call'}]},
 {'content': 'Error: Tool fmean not found',
  'role': 'tool',
  'tool_call_id': 'call_CCV3B1AQfrKyW3a2IR0k96zj'},
 {'content': 'Error: Tool fmean not found',
  'role': 'tool',
  'tool_call_id': 'call_MCmiKJuVhXBWtRV4JzVOmFPb'},
 {'content': 'テスト結果から標準偏差と偏差値を計算します。\n'
             '\n'
             'まず、各値の合計を計算します。\n'
             '\n'
             '- A氏: 90点\n'
             '- B氏: 70点\n'
             '- C氏: 40点\n'
             '- D氏: 65点\n'
             '\n'
             '合計点 = 90 + 70 + 40 + 65 = 265\n'
             '\n'
             '次に、平均点を計算します。\n'
             '平均点 = 合計点 / 人数 = 265 / 4 = 66.25\n'
             '\n'
             '次に、各人のスコアから平均を引き、その結果を二乗します。\n'
             '- A氏: (90 - 66.25)² = 594.0625\n'
             '- B氏: (70 - 66.25)² = 14.0625\n'
             '- C氏: (40 - 66.25)² = 676.5625\n'
             '- D氏: (65 - 66.25)² = 1.5625\n'
             '\n'
             'これらを合計します。\n'
             '594.0625 + 14.0625 + 676.5625 + 1.5625 = 1286.25\n'
             '\n'
             '次に、分散を計算します。\n'
             '分散 = 合計 / 人数 = 1286.25 / 4 = 321.5625\n'
             '\n'
             '最後に、標準偏差は分散の平方根です。\n'
             '標準偏差 = √321.5625 ≈ 17.9\n'
             '\n'
             '次に、偏差値を計算します。偏差値 = (得点 - 平均点) / 標準偏差 × 10 + 50\n'
             '\n'
             '- A氏: (90 - 66.25) / 17.9 × 10 + 50 ≈ 85.0\n'
             '- B氏: (70 - 66.25) / 17.9 × 10 + 50 ≈ 54.7\n'
             '- C氏: (40 - 66.25) / 17.9 × 10 + 50 ≈ 27.3\n'
             '- D氏: (65 - 66.25) / 17.9 × 10 + 50 ≈ 48.6\n'
             '\n'
             '**結果:**\n'
             '- 標準偏差: 約 17.9\n'
             '- A氏の偏差値: 約 85.0\n'
             '- B氏の偏差値: 約 54.7\n'
             '- C氏の偏差値: 約 27.3\n'
             '- D氏の偏差値: 約 48.6',
  'role': 'assistant',
  'tool_calls': []}]

```
</details>

でもね、ちょっとおかしくね？エクセルで検算してみたけど、おかしい・・・。きっと工夫のしようがあるな。
（ひとまず速報なのですみません）

いやぁ、bigtool、これは熱いな。（驚き屋かよ）

注意点があります。それは何度か実行してみましたが、tool_callingしっぱなしになったり、modelの呼び出し回数がかなり変動する印象です。
安定して動かすにはまだ少し工夫が必要だと思います。

<br>



# 5. おわりに
ということで、さくさくと速報レベルで書きました。
これ、現実問題として、工場の管理値とか、結構手作業でみんな計算しているんですよ。
ここに投げればよきに計らってくれるなんて、めっちゃ楽じゃないですか。
しかも、ガリガリプログラム書かなくてもいい。くそめんどくさいマクロも書かなくていい。そんな未来が来そうですね。
あとは入力方法がしっかりすれば便利になりそうです。

ということでまた。