LangChainのTool callingとStructured outputを試す

## はじめに
LangChainのChatOCIGenAIでTool callingとStructured outputがサポートされたので検証してみます。
Tool callingはいわゆるFunction callingの機能です。OCI SDK単体ではすでに同じ機能が使えるようになっていますがそのLangChain版です。
Structured outputはプロンプトに対して生成AIから返却される内容を事前に定義した構造化データで返却してくれるものです。例えば、LLMを介して中間フォーマットのjsonファイルを出力し、別システム(またはwebapiなど)に食わせるケースなどで重宝します。
この2つの機能を紹介していきます。

https://python.langchain.com/v0.2/docs/integrations/chat/oci_generative_ai/


## LangChain+OCI GenAIを改めて動かす

初期設定です。コンパートメントIDを入力します。
※このchatは最後まで使います。
```py:initiailze
from langchain_community.chat_models import ChatOCIGenAI

chat = ChatOCIGenAI(
        model_id="cohere.command-r-plus", 
        service_endpoint="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
        compartment_id="(my_compartment_id)",
        model_kwargs={"temperature": 0, "max_tokens": 500},
        is_strea
```

ストリーム形式で出力します。（爆速です）
```py:simple chat
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

messages = [
    SystemMessage(content="あなたは優秀なAIアシスタントです。"),
    AIMessage(content="こんにちわ！"),
    HumanMessage(content="AIに関するジョークを言ってください、その後その内容を解説して。"),
]

# response = chat.invoke(messages)
# print(response.content)
# print(response.response_metadata)
for chunk in chat.stream(messages):
    print(chunk.content, end="", flush=True)
```


```text:result
どうぞ：

「AIアシスタントが精神科医の代わりになるか実験したら、患者がみんな治って帰ってこなかった。」

解説：AIアシスタントは、人間の話し相手やアシスタントとして非常に優れた能力を持っています。
時には、人間の精神的な悩みや相談にも乗れるほどに進化しています。
このジョークは、AIアシスタントが精神科医の仕事を奪ってしまうのではないかという皮肉と、
AIアシスタントがそれほどまでに優秀で、人間の悩みを解決してしまうのなら、
精神科医のオフィスから患者がいなくなってしまうかもしれない、というユーモアを表現しています。
```



## Tool calling

シンプルな足し算、掛け算の関数を定義します。

```py: tool calling
from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """Adds a and b.

    Args:
        a: first int
        b: second int
    """
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b.

    Args:
        a: first int
        b: second int
    """
    return a * b

tools = [add, multiply]

# チャットモデルへtoolsをbindする
chat_with_tools = chat.bind_tools(tools)
```

以下の文章で実行します。
```py: 
from langchain_core.messages import ToolMessage

query = "3に12を乗じた値は？　また、11と49を足した値は何？"
messages = [HumanMessage(query)]
ai_msg = chat_with_tools.invoke(messages)
messages.append(ai_msg)
# print(ai_msg.content)
print(ai_msg.tool_calls) 

for tool_call in ai_msg.tool_calls:
    selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()]
    tool_output = selected_tool.invoke(tool_call["args"])
    messages.append(ToolMessage(tool_output, tool_call_id=tool_call["id"]))

final_response = chat_with_tools.invoke(messages)
print(final_response.content)
```
```text:result
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '7739f4a25a3f4ab5b93818de6392793a', 'type': 'tool_call'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': '8c219ef7e9454f6e9db6469ddd29d9e2', 'type': 'tool_call'}]
3に12を乗じた値は36です。11と49を足した値は60です。
```

足し算、掛け算はLLM単体でも実行できますが、処理結果から質問文の内容から定義した関数を使って答えを導いていることがわかります。

実際のユースケースでは様々なツールを作ることで可能性が広がります。
例えば、webapiを呼び出してハルシネーションを防いだり、あるいはagentとしてインプット内容を吟味したうえでDBへSQLを発行するなど、様々な応用が可能で興味深い機能です。

## Structured output

例えば、カスタマーサポートに来たメール文章を事前に定義した構造化データへパースすることが可能です。
```py
from langchain_core.pydantic_v1 import BaseModel, Field
import json

class Mail(BaseModel):
    """メール内容を構造化データにする"""

    sender: str = Field(description="メールの送信者")
    date: str = Field(description="メールが送信された日付")
    summary: str = Field(description="メール内容の要約")
    importance: str = Field(description="メールの重要度 (高, 中, 低など)")
    urgency: str = Field(description="メールの緊急度 (高, 中, 低など)")
    impression: str = Field(description="メールに対する所感や感想")
    nextaction: str = Field(description="メールに対する次のアクション")

structured_chat = chat.with_structured_output(Mail)

response = structured_chat.invoke("""
件名: サービスの不具合に対する早急な対応をお願いします

送信者: 山田太郎
送信日: 2024年8月16日

本文:

カスタマーサポート担当者様

お世話になっております。山田太郎と申します。

先週から御社のサービスを利用していますが、連続して重大な不具合に遭遇しており、非常に不満を感じています。具体的には、サービスの利用中に頻繁にシステムエラーが発生し、まともに利用できない状態です。サポートチームに何度も連絡を試みましたが、未だに問題は解決されていません。

このような状態では、サービスの継続利用を検討することすら難しいです。私は早急にこの問題が解決されることを強く求めています。もし、すぐに対応していただけない場合は、契約の解除と他社サービスへの移行を考えざるを得ません。

このメールを受け取り次第、至急対応していただきたくお願い申し上げます。具体的な対応策と今後の改善計画について、速やかにご連絡ください。

早急なご対応をお願い申し上げます。

山田太郎
""")

# print(response)
print(response.json(indent=4, ensure_ascii=False))
```

```py:result
{
    "sender": "山田太郎",
    "date": "2024年8月16日",
    "summary": "山田太郎さんは、御社のサービスで重大な不具合が発生していると報告し、早急な対応を求めています。サポートチームに連絡したが、まだ問題は解決されていません。山田さんは、問題が解決されなければ契約解除を検討すると述べています。",
    "importance": "高",
    "urgency": "高",
    "impression": "山田太郎さんは、御社のサービスに不満を抱いており、早急な対応を求めています。サポートチームに連絡済みとのことで、問題の解決が急がれます。",
    "nextaction": "サービスの不具合の原因を調査し、山田太郎さんに具体的な対応策と今後の改善計画を提示する。"
}
```

## おわりに
LangChainのChatOCIGenAIを使ったTool callingやStructured outputの機能は、LLMの可能性をさらに広げるものであり、単なる自然言語処理に留まらず、具体的なアクションや構造化されたデータ出力など、より実務的なシーンでも役立つ強力なツールです。
これらの機能を活用することで、AIのハルシネーションを防ぎつつ、さまざまなシステムやツールと連携した高度な自動化が可能となり、業務効率化に貢献できることが期待されます。
今後もこのような技術を駆使して、AIアシスタントがより幅広い用途で活躍できることを楽しみにしています。