Sunforger

Sunforger

langchain + ollama を初めて実行した際のいくつかの注意点

バージョンと設定#

ソフトウェアバージョン:

langchain                 0.3.25                   pypi_0    pypi
langchain-community       0.3.25                   pypi_0    pypi
langchain-core            0.3.65                   pypi_0    pypi
langchain-ollama          0.3.3                    pypi_0    pypi
langchain-openai          0.3.24                   pypi_0    pypi
langchain-text-splitters  0.3.8                    pypi_0    pypi

python バージョン:3.10.16

要件#

エピソディックなタスクを実行する必要があります。各ステップで、まず従来の方法で解決策を求めます。その後、大規模言語モデルを使用して解決策を最適化し、最終的な結果を観察します。

実装#

ファイル構造

src/llm
├── agents.py
├── keys.toml
├── models.py
├── prompts
│   ├── __init__.py
│   ├── [xxxx] // あなたのシナリオ名
│   │   ├── human.txt
│   │   └── system.txt
│   └── utils.py
└── tools.py

まずローカルの Ollama で使用し、通った後により強力なモデルとの接続を考えます。例えば、私の方ではまず qwen2:7b というモデルを使用しました。

# test/test_llm.py
from langchain.agents import create_structured_chat_agent

from src.llm.models import get_model
from src.llm.prompts.utils import load_prompts

def test_llm():
    model = get_model("ollama", "qwen2:7b")
    prompt = load_prompts(scenario="routing")

    agent = create_structured_chat_agent(model, [], prompt)
    result = agent.invoke({"input": "hello", "intermediate_steps": []})
    print(result)
# models.py
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI

def get_model(provider: str, model: str, temperature: float = 0.3):
    if provider == "ollama":
        return ChatOllama(
            model=model, temperature=temperature, base_url="http://localhost:11434" 
        ) # 引数に注意、llmはmodel引数に渡す必要があります。さもなければValidation Errorが発生します。
    elif provider == "openai":
        return ChatOpenAI(...) # 省略
    else:
        raise ValueError("unsupported provider")
# prompts/utils.py
from pathlib import Path

from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    SystemMessagePromptTemplate,
)

def load_prompts(scenario: str):
    path = Path(__file__).parent / scenario

    with open(path / "system.txt", "r", encoding="utf-8") as f:
        s_template = f.read()
        s_prompt = SystemMessagePromptTemplate.from_template(s_template)

    with open(path / "human.txt", "r", encoding="utf-8") as f:
        h_template = f.read()
        h_prompt = HumanMessagePromptTemplate.from_template(h_template)

    return ChatPromptTemplate.from_messages([s_prompt, h_prompt])

プロンプトファイル、参考リンク設定 https://smith.langchain.com/hub/hwchase17/structured-chat-agent

# human.txt

{input}

{agent_scratchpad}
(必ずJSONブロブで応答することを思い出してください)
# system.txt

人間にできるだけ役立ち、正確に応答してください。以下のツールにアクセスできます:
{tools}

アクションキー(ツール名)とアクション入力キー(ツール入力)を指定するためにJSONブロブを使用してください。
有効な「action」値: "Final Answer" または {tool_names}
$JSON_BLOBごとに1つのアクションのみを提供してください。次のように:

{{
  "action": $TOOL_NAME,
  "action_input": $INPUT
}}

この形式に従ってください:

質問:回答するための入力質問
考え:前のステップと次のステップを考慮する
アクション:

$JSON_BLOB

観察:アクション結果

... (考え/アクション/観察をN回繰り返す)

考え:何に応答するかを知っています
アクション:

{{
  "action": "Final Answer",
  "action_input": "人間への最終応答"
}}

始めましょう!常に単一のアクションの有効なJSONブロブで応答することを思い出してください。必要に応じてツールを使用してください。適切な場合は直接応答してください。形式はアクション:```$JSON_BLOB```その後観察

落とし穴#

1. バージョン#

langchain はまだ進化を続けており、以前のバージョンと現在のバージョンには一定のインターフェースの違いがありますので、特に注意が必要です。ネットで資料を調べる際には、タイムリーさが非常に重要です。

2. プロンプト#

create_structured_chat_agent関数を使用してエージェントを作成する際には、プロンプト内に必ず tools, tools_name, agent_scratchpad の 3 つのプレースホルダーが含まれていることに注意し、{} で囲む必要があります。

さもなければ、エラー ValueError: Prompt missing required variables: {'agent_scratchpad', 'tool_names', 'tools'} が発生します。

解析時に、このプロンプトテンプレートは f-string と見なされるため、{} 内の内容が自動的に変数値に置き換えられます。

更新 25/6/24#

従来の .txt をプロンプトとして使用するのは拡張性が非常に低い(コメントをサポートしていないため)ため、jinja2 テンプレートを使用してプロンプトを管理することにしました。

これにより、コメントをサポートし、プレースホルダーを解析できます。

jinja テンプレートは {{...}} をプレースホルダーとして使用しますが、これは langchain で使用される {...} とは異なります。この 2 つは混用できないため、解析エラーが発生します。

system promptについては、通常エージェントの初期化時に提供され、その後は変更されません。引数を渡す必要がある場合は、jinja テンプレートを介して渡すことをお勧めします。

from langchain_core.prompts.string import jinja2_formatter

s_template = jinja2_formatter(
  Path(path / "system.jinja").read_text(encoding="utf-8"),
  ... # プレースホルダーに対応する引数を渡す  
)
s_prompt = SystemMessagePromptTemplate.from_template(s_template)

human prompt、つまりエージェントの入力については、通常は各ステップでリアルタイムに更新されるため、jinja を使用して読み込むことも、.invoke() メソッドを介して渡すこともできます。ただし、前者ではプレースホルダーを {{...}} 形式で記述し、system prompt と同様の方法で human prompt をインポートする必要があります。後者では、langchain スタイルの {...} をプレースホルダーとして使用し、文字列の形式で直接インポートする必要があります(以下のように)。

h_template = Path(path / "human.jinja").read_text(encoding="utf-8")
h_prompt = HumanMessagePromptTemplate.from_template(h_template)

3. 呼び出し#

agent.invoke() を実行する際には、input フィールドの内容を提供するだけでなく、intermediate_steps フィールドの値も提供する必要があります。

さもなければ、エラー KeyError: 'intermediate_steps' が発生します。

更新#

AgentExecutor を使用してエージェントをラップすることで、より詳細なカスタマイズが可能になり、デバッグ情報を返すことができます。

## 元のコード
agent = create_structured_chat_agent(model, [], prompt)
result = agent.invoke({"input": "hello", "intermediate_steps": []})

## 更新されたコード
agent = create_structured_chat_agent(model, [], prompt)
executor = AgentExecutor.from_agent_and_tools(
    agent=agent, tools=[], verbose=True, return_intermediate_steps=True
)
result = executor.invoke({"input": "hello"})

より詳細なカスタマイズが可能になる一方で、intermediate_steps パラメータを手動で渡す必要がなくなります。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。