LangChain を使って Function Call してみた

# はじめに

「AI エージェント」を使おう、とよく聞きます。また、にわかに「MCP」が話題になっています。
「AI エージェント」「MCP」について調べると、「Function Calling」が紹介されました。
まず「Function Calling」が何か調べたいと思います。

# Fuction Calling とは

「function」は「機能」でしょうか「関数」でしょうか。これを生成 AI に「呼出」させるようですね。

`Function Calling` は、OpenAI 社が ChatGPT API サービスに追加した機能のようです (2023 年) 。Google Gemini 、Amazon Bedrock 、Anthropic Claude でも、Function Calling（または Tool Use）できるようになっています。

LangChain の `Agents` 機能で `Tools` 呼出することで、同様の機能を実現できるようです。これを試してみたいと思います。

# LangChain＋Ollama を使ってみる

`Function Calling` の実装を試すと、AI モデルを何度も呼出することになりそうです。ChatGPT API など有料サービスを使っているとつらそうです。ローカルで AI モデルを呼出できる環境がよさそうです。そのために Ollama を使うことにします。

[【Ollama】ローカル環境でLLMを選んでLangChainで実行する。](https://zenn.dev/buenotheebiten/articles/7c351b06494be0)

## 実行環境を用意する

以前と同様に Google Colab を使ってみます。別の環境でも構いません。

参考：[生成 AI プログラムを試してみた #LLM - Qiita](https://qiita.com/tinymouse/items/ed64a452bcddf4b8af7d#google-colab-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)

Google Colab のランタイムは「T4」を使いました。

以下のコード例は、Google Colab あるいは Jupyter Notebook で実行するものです。

## Ollama を導入する

Ollama は実行ツールでありライブラリのようですね。

[Ollama](https://ollama.com/)

まず Ollama 一式をインストールします。

```shell
# Ollama をインストール

! curl -fsSL https://ollama.com/install.sh | sh
```

コマンドを利用したり Python コードから呼出するには、Ollama プログラムを実行して常駐させるようです。Google Colab で実行するときは、`nohup` で実行します。↓

```shell
# Ollama を実行

! nohup ollama serve &
```

利用したい AI モデルを指定してダウンロードしておきます。まず `Llama 3` を使うことにします。↓

```shell
# モデルをダウンロード

! ollama pull llama3.2
```

## LangChain で Ollama を実行する

Ollama を LangChain で実行してみます。

```shell
# 必要なライブラリをインストール
! pip install langchain_ollama
```

```python
import langchain_ollama
import langchain_core.prompts

# モデルを準備
model = langchain_ollama.OllamaLLM(
    model="llama3.2"
)

# プロンプトを準備
prompt = langchain_core.prompts.chat.ChatPromptTemplate.from_template(
"""Question: {query}
Answer:"""
)

query = "Function Calling とは何ですか？"

# 推論を実行
chain = (prompt | model)
output = chain.invoke({"query": query})

# 結果を出力
print(output)
```

# LangChain で Function Call してみる

LangChain を使って `Function Call` してみましょう。

[【Ollama】【LangChain】Function callingについて勉強してみる - パソコン関連もろもろ](https://touch-sp.hatenablog.com/entry/2025/01/04/130220)

## Function（機能／関数）を用意する

まず、`function（機能／関数）` を用意します。LangChain の `Tools（ツール）` 機能を使います。

```shell
# 必要なライブラリをインストール
! pip install langchain langchain_core langchain_community
```

```python
from langchain.tools import tool

# 関数を用意
@tool
def add_numbers(x: int, y: int) -> int:
    """Add two numbers together."""
    return x + y

tools = [add_numbers]
```

簡単に「足し算する」関数です。↑

## Agent（エージェント）を実行する

続いて、LangChain の `Agent（エージェント）` を用意します。

```python
import langchain_ollama
import langchain.agents
import langchain_core.prompts

# モデルを準備
model = langchain_ollama.ChatOllama(
    model="llama3.2"
)

# プロンプトを準備
prompt = langchain_core.prompts.chat.ChatPromptTemplate.from_template(
"""Question: {query}
Answer:"""
)

# エージェントを用意
agent = langchain.agents.create_tool_calling_agent(model, tools, prompt)
executor = langchain.agents.AgentExecutor(agent=agent, tools=tools)
```

事前に用意した `tools` オブジェクトをエージェントに渡しています。↑

用意したエージェントを実行します。

```python
query = "9 に 3 を加えると幾つですか？"

# 経過を表示
for step in executor.stream({"query": query}):
   print(step)

# 推論を実行
output = executor.invoke({"query": query})
print(output)
```

```
ValueError: Prompt missing required variables: {'agent_scratchpad'}
```

エラーになりました。プロンプトに `{'agent_scratchpad'}` が必要なようです。↑

```python
(前略)

# プロンプトを準備
prompt = langchain_core.prompts.chat.ChatPromptTemplate.from_template(
"""Question: {query}
Answer: {agent_scratchpad}"""
)

(後略)
```

実行し直します。

```
{'actions': [ToolAgentAction(tool='add_numbers', tool_input={'x': '9', 'y': '3'}, log="\nInvoking: `add_numbers` with `{'x': '9', 'y': '3'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:02.615428806Z', 'done': True, 'done_reason': 'stop', 'total_duration': 536174289, 'load_duration': 26289245, 'prompt_eval_count': 179, 'prompt_eval_duration': 21685451, 'eval_count': 23, 'eval_duration': 487499311, 'model_name': 'llama3.2'}, id='run-0b9da736-76f1-4ac8-8173-3a2e56180fd9', tool_calls=[{'name': 'add_numbers', 'args': {'x': '9', 'y': '3'}, 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'type': 'tool_call'}], usage_metadata={'input_tokens': 179, 'output_tokens': 23, 'total_tokens': 202}, tool_call_chunks=[{'name': 'add_numbers', 'args': '{"x": "9", "y": "3"}', 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='250484c7-0076-4539-b663-156f2cfb5ad9')], 'messages': [AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:02.615428806Z', 'done': True, 'done_reason': 'stop', 'total_duration': 536174289, 'load_duration': 26289245, 'prompt_eval_count': 179, 'prompt_eval_duration': 21685451, 'eval_count': 23, 'eval_duration': 487499311, 'model_name': 'llama3.2'}, id='run-0b9da736-76f1-4ac8-8173-3a2e56180fd9', tool_calls=[{'name': 'add_numbers', 'args': {'x': '9', 'y': '3'}, 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'type': 'tool_call'}], usage_metadata={'input_tokens': 179, 'output_tokens': 23, 'total_tokens': 202}, tool_call_chunks=[{'name': 'add_numbers', 'args': '{"x": "9", "y": "3"}', 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'index': None, 'type': 'tool_call_chunk'}])]}
{'steps': [AgentStep(action=ToolAgentAction(tool='add_numbers', tool_input={'x': '9', 'y': '3'}, log="\nInvoking: `add_numbers` with `{'x': '9', 'y': '3'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:02.615428806Z', 'done': True, 'done_reason': 'stop', 'total_duration': 536174289, 'load_duration': 26289245, 'prompt_eval_count': 179, 'prompt_eval_duration': 21685451, 'eval_count': 23, 'eval_duration': 487499311, 'model_name': 'llama3.2'}, id='run-0b9da736-76f1-4ac8-8173-3a2e56180fd9', tool_calls=[{'name': 'add_numbers', 'args': {'x': '9', 'y': '3'}, 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'type': 'tool_call'}], usage_metadata={'input_tokens': 179, 'output_tokens': 23, 'total_tokens': 202}, tool_call_chunks=[{'name': 'add_numbers', 'args': '{"x": "9", "y": "3"}', 'id': '250484c7-0076-4539-b663-156f2cfb5ad9', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='250484c7-0076-4539-b663-156f2cfb5ad9'), observation=12)], 'messages': [FunctionMessage(content='12', additional_kwargs={}, response_metadata={}, name='add_numbers')]}
(中略)
{'output': '答えは 12', 'messages': [AIMessage(content='答えは 12', additional_kwargs={}, response_metadata={})]}
```

経過を表示すると、`add_numbers` を呼出しているようです。結果は以下になりました。↓

```
{'query': '9 に 3 を加えると幾つですか？', 'output': '最終的な答えは12です。'}
```

## 別の function を試す

足し算では、素の AI モデルでも正しい答えを出せそうです。AI モデルが知っていなさそうなキーワードに対して回答するのはどうでしょうか。

```python
(前略)

@tool
def get_information(word: str) -> str:
    """Get information about the given keyword."""
    match word:
        case "Tiny Mouse":
            answer = "SI 企業の SE であり、日曜プログラマであり、二児の父。"
        case "なごや個人開発者の集い":
            answer = "毎週日曜日に開催する定例オフライン開発会です。 ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし"
        case _:
            answer = "不明"
    return answer

tools = [get_information]
```

これを使ってみます。実行するコードは同じです。

```python
(前略)

query = "なごや個人開発者の集いとは何ですか？"

(後略)
```

結果は↓

```
{'actions': [ToolAgentAction(tool='get_information', tool_input={'word': '本盤海彿砟'}, log="\nInvoking: `get_information` with `{'word': '本盤海彿砟'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:27.297408747Z', 'done': True, 'done_reason': 'stop', 'total_duration': 830074741, 'load_duration': 24190237, 'prompt_eval_count': 171, 'prompt_eval_duration': 20889432, 'eval_count': 42, 'eval_duration': 784285291, 'model_name': 'llama3.2'}, id='run-33de3e90-b710-4c94-a3a9-e6a842d41bdc', tool_calls=[{'name': 'get_information', 'args': {'word': '本盤海彿砟'}, 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 42, 'total_tokens': 213}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u79c1\\u306f\\u672c\\u76e4\\u6d77\\u5f7f\\u781f"}', 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='a00b20e9-45d8-4cf5-9180-9aa4a4e71370')], 'messages': [AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:27.297408747Z', 'done': True, 'done_reason': 'stop', 'total_duration': 830074741, 'load_duration': 24190237, 'prompt_eval_count': 171, 'prompt_eval_duration': 20889432, 'eval_count': 42, 'eval_duration': 784285291, 'model_name': 'llama3.2'}, id='run-33de3e90-b710-4c94-a3a9-e6a842d41bdc', tool_calls=[{'name': 'get_information', 'args': {'word': '本盤海彿砟'}, 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 42, 'total_tokens': 213}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u79c1\\u306f\\u672c\\u76e4\\u6d77\\u5f7f\\u781f"}', 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'index': None, 'type': 'tool_call_chunk'}])]}
{'steps': [AgentStep(action=ToolAgentAction(tool='get_information', tool_input={'word': '本盤海彿砟'}, log="\nInvoking: `get_information` with `{'word': '本盤海彿砟'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'llama3.2', 'created_at': '2025-04-22T12:00:27.297408747Z', 'done': True, 'done_reason': 'stop', 'total_duration': 830074741, 'load_duration': 24190237, 'prompt_eval_count': 171, 'prompt_eval_duration': 20889432, 'eval_count': 42, 'eval_duration': 784285291, 'model_name': 'llama3.2'}, id='run-33de3e90-b710-4c94-a3a9-e6a842d41bdc', tool_calls=[{'name': 'get_information', 'args': {'word': '本盤海彿砟'}, 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 42, 'total_tokens': 213}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u79c1\\u306f\\u672c\\u76e4\\u6d77\\u5f7f\\u781f"}', 'id': 'a00b20e9-45d8-4cf5-9180-9aa4a4e71370', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='a00b20e9-45d8-4cf5-9180-9aa4a4e71370'), observation={'information': '不明'})], 'messages': [FunctionMessage(content='{"information": "不明"}', additional_kwargs={}, response_metadata={}, name='get_information')]}
(中略)
{'output': 'This is a JSON array of objects, each representing an "AIMessageChunk" object. The AIMessageChunk object contains the following properties:\n\n* `name`: a string representing the name of the tool call\n* `args`: a dictionary containing the arguments passed to the tool call\n* `id`: a unique identifier for the tool call\n\nThe JSON array appears to be a sequence of tool calls, where each tool call is represented by an AIMessageChunk object. The tools seem to be related to natural language processing or text analysis.\n\nHere\'s a sample Python code snippet that parses this JSON array:\n```python\nimport json\n\n# assume \'message_chunks\' is the JSON array from the original response\nmessage_chunks = [...]  # replace with the actual JSON array\n\nfor chunk in message_chunks:\n    name = chunk[\'name\']\n    args = chunk[\'args\']\n    id = chunk[\'id\']\n\n    print(f"Tool: {name}")\n    print(f"Arguments: {args}")\n    print(f"ID: {id}")\n    print("--------")\n```\nThis code snippet uses a for loop to iterate over the `message_chunks` array and prints out the name, arguments, and ID of each tool call. The output will look something like this:\n```\nTool: get_lemma\nArguments: {\'word\': \'\\u304c\\u3061\\u3061\\u306f\', \'lang\': \'ja\'}\nID: f14e0156-7478-485a-91dc-30a2e89a7fcd\n--------\nTool: get_sense\nArguments: {\'word\': \'\\u304c\\u3061\\u3061\\u306f\', \'lang\': \'ja\'}\nID: bd752da1-e62b-44e3-b0e3-8cd099c31e15\n--------\n```\nNote that the actual output will depend on the specific tools and their arguments.', 'messages': [AIMessage(content='This is a JSON array of objects, each representing an "AIMessageChunk" object. The AIMessageChunk object contains the following properties:\n\n* `name`: a string representing the name of the tool call\n* `args`: a dictionary containing the arguments passed to the tool call\n* `id`: a unique identifier for the tool call\n\nThe JSON array appears to be a sequence of tool calls, where each tool call is represented by an AIMessageChunk object. The tools seem to be related to natural language processing or text analysis.\n\nHere\'s a sample Python code snippet that parses this JSON array:\n```python\nimport json\n\n# assume \'message_chunks\' is the JSON array from the original response\nmessage_chunks = [...]  # replace with the actual JSON array\n\nfor chunk in message_chunks:\n    name = chunk[\'name\']\n    args = chunk[\'args\']\n    id = chunk[\'id\']\n\n    print(f"Tool: {name}")\n    print(f"Arguments: {args}")\n    print(f"ID: {id}")\n    print("--------")\n```\nThis code snippet uses a for loop to iterate over the `message_chunks` array and prints out the name, arguments, and ID of each tool call. The output will look something like this:\n```\nTool: get_lemma\nArguments: {\'word\': \'\\u304c\\u3061\\u3061\\u306f\', \'lang\': \'ja\'}\nID: f14e0156-7478-485a-91dc-30a2e89a7fcd\n--------\nTool: get_sense\nArguments: {\'word\': \'\\u304c\\u3061\\u3061\\u306f\', \'lang\': \'ja\'}\nID: bd752da1-e62b-44e3-b0e3-8cd099c31e15\n--------\n```\nNote that the actual output will depend on the specific tools and their arguments.', additional_kwargs={}, response_metadata={})]}
```

上手く行きません。AI モデルが非力だと起きるようです。↑

Ollama のリポジトリからダウンロードされた `Llama 3.2` は 3b で 2.0 GB です。
これより容量が大きい（容量が大きければ賢いとは限らないが）`Gemma 3` モデルを使ってみます。4b で 3.3 GB でした。

```shell
# モデルをダウンロード

! ollama pull gemma3
```

これを使ってみます。実行するコードは同じです。

```python
(前略)

# モデルを準備
model = langchain_ollama.ChatOllama(
    model="gemma3"
)

(後略)
```

結果は↓

```
ResponseError: registry.ollama.ai/library/gemma3:4b does not support tools (status code: 400)
```

Tools 機能を使える AI モデルと使えないモデルがあるようです。↑

Ollama のリポジトリで探してみると、`Qwen 2` モデルが使えそうです。7b で 4.7 GB でした。

```shell
# モデルをダウンロード

! ollama pull qwen2.5
```

これを使ってみます。実行するコードは同じです。

```python
(前略)

# モデルを準備
model = langchain_ollama.ChatOllama(
    model="qwen2.5"
)

(後略)
```

結果は↓

```
{'output': '申し訳ありませんが、"なごや個人開発者的集い"について具体的な情報が見つかりませんでした。これについては詳しく知っていますか？それによって、より詳細な情報を調査することができます。', 'messages': [AIMessage(content='申し訳ありませんが、"なごや個人開発者的集い"について具体的な情報が見つかりませんでした。これについては詳しく知っていますか？それによって、より詳細な情報を調査することができます。', additional_kwargs={}, response_metadata={})]}
```

事前に用意した `get_information` が呼出されていません。↑

用意されたツールを使うよう、プロンプトに追記してみます。↓

```python
(前略)

# プロンプトを準備
prompt = langchain_core.prompts.chat.ChatPromptTemplate.from_template(
"""Question: {query}
Thought: Let's think step by step.
Use one of registered tools to answer the question.
Answer: {agent_scratchpad}"""
)

(後略)
```

結果は↓

```
{'actions': [ToolAgentAction(tool='get_information', tool_input={'word': 'なごや個人開発者の集い'}, log="\nInvoking: `get_information` with `{'word': 'なごや個人開発者の集い'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-04-22T23:43:33.722054618Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3386839140, 'load_duration': 21847772, 'prompt_eval_count': 248, 'prompt_eval_duration': 62191870, 'eval_count': 110, 'eval_duration': 3296596949, 'model_name': 'qwen2.5'}, id='run-86b44335-1cd7-4741-bef6-ee9345fc4e71', tool_calls=[{'name': 'get_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'type': 'tool_call'}], usage_metadata={'input_tokens': 248, 'output_tokens': 110, 'total_tokens': 358}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='bd807f43-5718-40a6-a7ef-d2497158f09e')], 'messages': [AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-04-22T23:43:33.722054618Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3386839140, 'load_duration': 21847772, 'prompt_eval_count': 248, 'prompt_eval_duration': 62191870, 'eval_count': 110, 'eval_duration': 3296596949, 'model_name': 'qwen2.5'}, id='run-86b44335-1cd7-4741-bef6-ee9345fc4e71', tool_calls=[{'name': 'get_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'type': 'tool_call'}], usage_metadata={'input_tokens': 248, 'output_tokens': 110, 'total_tokens': 358}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'index': None, 'type': 'tool_call_chunk'}])]}
{'steps': [AgentStep(action=ToolAgentAction(tool='get_information', tool_input={'word': 'なごや個人開発者の集い'}, log="\nInvoking: `get_information` with `{'word': 'なごや個人開発者の集い'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-04-22T23:43:33.722054618Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3386839140, 'load_duration': 21847772, 'prompt_eval_count': 248, 'prompt_eval_duration': 62191870, 'eval_count': 110, 'eval_duration': 3296596949, 'model_name': 'qwen2.5'}, id='run-86b44335-1cd7-4741-bef6-ee9345fc4e71', tool_calls=[{'name': 'get_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'type': 'tool_call'}], usage_metadata={'input_tokens': 248, 'output_tokens': 110, 'total_tokens': 358}, tool_call_chunks=[{'name': 'get_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'bd807f43-5718-40a6-a7ef-d2497158f09e', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='bd807f43-5718-40a6-a7ef-d2497158f09e'), observation={'information': '毎週日曜日に開催する定例オフライン開発会です。 ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし'})], 'messages': [FunctionMessage(content='{"information": "毎週日曜日に開催する定例オフライン開発会です。 ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし"}', additional_kwargs={}, response_metadata={}, name='get_information')]}
{'output': 'なごや個人開発者の集いは、毎週日曜日に開催される定例のオフライン開発会です。ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々も参加し、もくもく作業することができたり、開発や制作に関する相談をしたり、作品の自慢をすることができます。また、協力してプロダクトを作ることもできます。', 'messages': [AIMessage(content='なごや個人開発者の集いは、毎週日曜日に開催される定例のオフライン開発会です。ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々も参加し、もくもく作業することができたり、開発や制作に関する相談をしたり、作品の自慢をすることができます。また、協力してプロダクトを作ることもできます。', additional_kwargs={}, response_metadata={})]}
```

上手くできました。↑

## AI モデルを変えてみる

さらに AI モデルを変えてみます。`Gemini API` を使ってみたいと思います。

参考 [LangChain の Gemini統合 を試す｜npaka](https://note.com/npaka/n/n7a3012b11a95)

```shell
# 必要なライブラリをインストール
! pip install langchain-google-genai
```

```python
import os
os.environ["GOOGLE_API_KEY"] = (予め取得した API キー)
```

```python
import langchain_google_genai
import langchain_core.prompts
import langchain.agents

# モデルを準備
model = langchain_google_genai.ChatGoogleGenerativeAI(
    model="gemini-2.0-flash"
)

(後略)
```

結果は↓

```
{'output': 'なごや個人開発者の集いについてですが、申し訳ありません。検索するための適切なキーワードがわかりませんでした。なにかお手伝いできることはありますか？', 'messages': [AIMessage(content='なごや個人開発者の集いについてですが、申し訳ありません。検索するための適切なキーワードがわかりませんでした。なにかお手伝いできることはありますか？', additional_kwargs={}, response_metadata={})]}
```

事前に用意した `get_information` が呼出されていません。↑

以下のようにすると上手く行きました。

```python
@tool
def ask_information(word: str) -> str:
    """Get information about the given keyword."""
(中略)

tools = [ask_information]
```

`get_information` でなく `ask_information` にしました。↑

```
{'actions': [ToolAgentAction(tool='ask_information', tool_input={'word': 'なごや個人開発者の集い'}, log="\nInvoking: `ask_information` with `{'word': 'なごや個人開発者の集い'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={'function_call': {'name': 'ask_information', 'arguments': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}'}}, response_metadata={'finish_reason': 'STOP', 'model_name': 'gemini-2.0-flash', 'safety_ratings': []}, id='run-e203c061-5370-4743-9f54-e3873fb3d90f', tool_calls=[{'name': 'ask_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'type': 'tool_call'}], usage_metadata={'input_tokens': 52, 'output_tokens': 12, 'total_tokens': 64, 'input_token_details': {'cache_read': 0}}, tool_call_chunks=[{'name': 'ask_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='efafa38d-8912-4c52-b966-cdf35a554901')], 'messages': [AIMessageChunk(content='', additional_kwargs={'function_call': {'name': 'ask_information', 'arguments': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}'}}, response_metadata={'finish_reason': 'STOP', 'model_name': 'gemini-2.0-flash', 'safety_ratings': []}, id='run-e203c061-5370-4743-9f54-e3873fb3d90f', tool_calls=[{'name': 'ask_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'type': 'tool_call'}], usage_metadata={'input_tokens': 52, 'output_tokens': 12, 'total_tokens': 64, 'input_token_details': {'cache_read': 0}}, tool_call_chunks=[{'name': 'ask_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'index': None, 'type': 'tool_call_chunk'}])]}
{'steps': [AgentStep(action=ToolAgentAction(tool='ask_information', tool_input={'word': 'なごや個人開発者の集い'}, log="\nInvoking: `ask_information` with `{'word': 'なごや個人開発者の集い'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={'function_call': {'name': 'ask_information', 'arguments': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}'}}, response_metadata={'finish_reason': 'STOP', 'model_name': 'gemini-2.0-flash', 'safety_ratings': []}, id='run-e203c061-5370-4743-9f54-e3873fb3d90f', tool_calls=[{'name': 'ask_information', 'args': {'word': 'なごや個人開発者の集い'}, 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'type': 'tool_call'}], usage_metadata={'input_tokens': 52, 'output_tokens': 12, 'total_tokens': 64, 'input_token_details': {'cache_read': 0}}, tool_call_chunks=[{'name': 'ask_information', 'args': '{"word": "\\u306a\\u3054\\u3084\\u500b\\u4eba\\u958b\\u767a\\u8005\\u306e\\u96c6\\u3044"}', 'id': 'efafa38d-8912-4c52-b966-cdf35a554901', 'index': None, 'type': 'tool_call_chunk'}])], tool_call_id='efafa38d-8912-4c52-b966-cdf35a554901'), observation={'information': '毎週日曜日に開催する定例オフライン開発会です。 ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし'})], 'messages': [FunctionMessage(content='{"information": "毎週日曜日に開催する定例オフライン開発会です。 ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし"}', additional_kwargs={}, response_metadata={}, name='ask_information')]}
{'output': '名古屋個人開発者の集いは、毎週日曜日に開催される定例オフライン開発会です。ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし、という集まりです。', 'messages': [AIMessage(content='名古屋個人開発者の集いは、毎週日曜日に開催される定例オフライン開発会です。ソフト・ハードのエンジニアだけでなく、デザイナー、クリエイター、マーケターの方々が集い、もくもく作業するもよし、開発・制作の相談をするもよし、単に作品の自慢をするもよし、協力してプロダクトを作るもよし、という集まりです。', additional_kwargs={}, response_metadata={})]}
```

上手くできました。↑

# Function Calling とは

`Function Calling` は、以下の手順で実行されます。

[Function calling - OpenAI API](https://platform.openai.com/docs/guides/function-calling)

①`機能／関数（ツール）`を使えることを LLM に伝えておく
②LLM は必要に応じて`機能／関数`の呼出を要求する
③`機能／関数`を呼出する
④`機能／関数`で得られた結果を LLM に伝える
⑤LLM が回答する

![説明①.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/112746f3-09ca-436c-a2ec-09bdb7f32091.png)
![説明②.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/bfb15fcf-f06b-4f60-abc1-258a032b5976.png)

`機能／関数（ツール）`は、外部サービスを参照してもいいし、外部コマンドを実行することもできます。
これを LLM が必要に応じて呼出できるようにすることで、LLM がアプリの機能を操作できるようになります。

Cursor や Cline は、エディタを操作できるよう `機能／関数（ツール）` が仕込まれていて、LLM の指示に従って`機能を呼出（Function Calling）`しているわけですね。