LangChain(Python)のPrompt基本使い方

いつも適当にLangChainのプロンプトを使っていたので、少し整理してみました。似たようなクラスも多いので頭の中がすっきりしました。


# 使用例
## 基本
一番基本的な使い方かと思います。`system`にLLMの役割を記述して、`human`にはLLMに投げかけるプロンプトを書きます。

```python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
messages = [
    ("system", "You are a helpful assistant"),
    ("human", "Hi")
]
response = llm.invoke(messages)
print(response.content)
```

```terminal: print(response.content)
Hello! How can I assist you today?
```

たまに`human`ではなく以下のように`user`と書いてあるのも見たことがあります。時間ないので細かく調べていませんが、動作は`human`と同じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/898011a5-fe29-b671-ef6f-3d88722f47d6.png)

[LangChain公式の「Messages」](https://python.langchain.com/v0.2/docs/concepts/#messages)には、それぞれ、以下のように書いています。

SystemMessage
> This represents a system message, which tells the model how to behave. Not every model provider supports this.

HumanMessage
> This represents a message from the user.

## Messageクラスの使用
基本と同じことをクラス`SystemMessage`と`HumanMessage`を使って実装。書き方が違うだけで特に機能に変化なしです。ただ、こちらの書き方の方が、リテラルよりも保守性が高そうに思います。

```python
from langchain_core.messages import HumanMessage, SystemMessage

messages = [
    SystemMessage(content="You are a helpful assistant"),
    HumanMessage(content="Hi")
]
response = llm.invoke(messages)
print(response.content)
```

## Prompt Template(変数ありPrompt)
実際には変数に値を埋め込むような使い方が多いと思います。[`PromptpTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.prompt.PromptTemplate.html)を使います。

```python
from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template("Call me {name}")
prompt = prompt_template.invoke({"name": "太郎"})
print(prompt.text)
```

```:print(prompt.text)
Call me 太郎
```

[`PromptpTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.prompt.PromptTemplate.html)だけを使って、LLMにPromptを投げます。そのときに変数に辞書形式で値を渡します。この方式だと勝手にプロンプトのRoleが`human`になってくれるみたいです(詳細未確認)。

```python
chain = prompt_template | llm
response = chain.invoke({"name": "太郎"})
print(response.content)
```

```:print(response.content)
Hello 太郎! How can I assist you today?
```

## ChatPromptTemplates
[ChatPromptTemplates](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html)を使うと、複数メッセージを送れます。

```python
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
    SystemMessage(content='You are a helpful assistant'),
    HumanMessagePromptTemplate.from_template("Call me {name}")
])
prompt = prompt_template.invoke({"name": "太郎"})
print(prompt.messages)
```

```print(prompt.messages)
[SystemMessage(content='You are a helpful assistant'),
HumanMessage(content='Call me 太郎')]
```

## MessagesPlaceholder
[MessagesPlaceholder](https://api.python.langchain.com/en/latest/prompts/langchain_core.prompts.chat.MessagesPlaceholder.html)を使うことでテキストとしての変数ではなく、メッセージそのものを変数化できます。

```python
from langchain_core.prompts import MessagesPlaceholder

prompt_template = ChatPromptTemplate.from_messages([
    SystemMessage(content='You are a helpful assistant'),
    MessagesPlaceholder("msgs")
])

prompt = prompt_template.invoke({"msgs": 
    [HumanMessage(content="hi! my name is 太郎")]})
print(prompt.messages)
```

```:print(prompt.messages)
[SystemMessage(content='You are a helpful assistant'),
HumanMessage(content='hi! my name is 太郎')]
```

これは、会話履歴を渡すときによく使います。


```python: 会話履歴パターン
from langchain_core.messages import AIMessage
from langchain_core.prompts.chat import HumanMessagePromptTemplate

prompt_template = ChatPromptTemplate.from_messages(
    [
        SystemMessage(content="You are a helpful assistant"),
        MessagesPlaceholder("history"),
        HumanMessagePromptTemplate.from_template("{question}")
    ]
)
history = [
        HumanMessage(content="hi! my name is 太郎"),
        AIMessage(content="hello 太郎"),
        ]

prompt = prompt_template.invoke(
   {
       "history": history,
       "question": "Call me my name."
   }
)
print(prompt.messages)
```

```:print(prompt.messages)
[SystemMessage(content='You are a helpful assistant'),
HumanMessage(content='hi! my name is 太郎'),
AIMessage(content='hello 太郎'),
HumanMessage(content='Call me my name.')]
```

長くなってくると`pretty_print`関数使うことで少し見やすい

```python
prompt_template.pretty_print()
```

```:prompt_template.pretty_print()
================================ System Message ================================

You are a helpful assistant

============================= Messages Placeholder =============================

{history}

================================ Human Message =================================

{question}
```

会話履歴がLLMに伝わっているか確認。

```python
chain = prompt_template | llm
response = chain.invoke(
   {
       "history": history,
       "question": "Call me my name."
   })
print(response.content)
```

履歴を踏まえた回答が返ってきていますね。

```:print(response.content)
Of course, 太郎. How can I assist you today?
```

使ったことはないですが、Example Selectors というFew-Shot Promptingの機能もあるようです。

https://python.langchain.com/v0.2/docs/how_to/example_selectors/

## LangChain Hub
[LangChain Hub](https://smith.langchain.com/hub)というPromptのHubがあります。Function Callingする場合など、ここのPromptから取得/参考にして使っています。
Python上で呼び出す方法。

```python
from langchain import hub
prompt_template = hub.pull("rlm/rag-prompt")
prompt_template.pretty_print()
```

```:prompt_template.pretty_print()
================================ Human Message =================================

You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.
Question: {question} 
Context: {context} 
Answer:
```

invokeすることで変数埋め込むのは他と同じ。

```python
prompt = prompt_template.invoke({"question": "What is my name?", 
                                 "context": "My name is 太郎"})
prompt.to_messages()
```

コードブロックだと横スクロールが見にくいと思い、表形式に。

|prompt.to_messages()|
|:--|
|[HumanMessage(content="You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\nQuestion: What is my name? \nContext: My name is 太郎 \nAnswer:")]