⛓️

Ruby版LangchainのLangchainrbをさわってみる(チャット編)

2024/06/19に公開

はじめに

LLM 関係のライブラリとして Langchain が有名ですが、本家(?)は Python と TypeScript のみです。
普段は Ruby をメインで書いているため Ruby でも LLM を Langchain 相当のことをしたいと思い調べていると Ruby 版 Langchain として Langchainrb が開発されていることを知りました。

https://github.com/patterns-ai-core/langchainrb

本記事では Langchainrb の基本的な使い方を紹介したいと思います。
(バージョン:0.13.4)

サポートしている LLM

上記が記事執筆時点で Langchainrb がサポートしている LLM です。
私はまだ OpenAI の LLM しか触ったことがないのですが、有名な LLM は大体サポートされている感じでしょうか。

Chat

ここでは OpenAI の LLM で基本的なチャットを試してみます。
準備として Langchainrb の gem 以外に ruby-openai の gem が必要となります。

gem "ruby-openai", "~> 6.3.0"

単純に LLM からのレスポンスを受け取る場合は以下の記述だけです。
とても簡単ですね。

llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
llm.chat(messages: [{role: "user", content: "こんにちは!"}])

chat のレスポンスは次のような形式になります。

#<Langchain::LLM::OpenAIResponse:0x0000000111c33630
 @model=nil,
 @raw_response=
  {"id"=>"chatcmpl-9bFvjKlAIXLOS9LEOJL784QwI4kVT",
   "object"=>"chat.completion",
   "created"=>1718666399,
   "model"=>"gpt-3.5-turbo-0125",
   "choices"=>
    [{"index"=>0,
      "message"=>{"role"=>"assistant", "content"=>"こんにちは!お元気ですか?何かお手伝いできることがありますか?"},
      "logprobs"=>nil,
      "finish_reason"=>"stop"}],
   "usage"=>{"prompt_tokens"=>8, "completion_tokens"=>25, "total_tokens"=>33},
   "system_fingerprint"=>nil}>

Langchain::LLM::OpenAIResponse クラスには completion というメソッドが実装されており、メッセージのみを取り出すことができます。

llm.chat(messages: [{role: "user", content: "こんにちは!"}]).completion
# "こんにちは!お元気ですか?何かお手伝いできることがありますか?"

https://github.com/patterns-ai-core/langchainrb/blob/e84785509a95ec2ac8236e5cf77f66987defb735/lib/langchain/llm/response/openai_response.rb#L15-L17

上記の例では LLM のインスタンスに api_key しか渡しませんでしたが、当然モデルの指定をすることが可能です。
モデルを指定する場合は default_options の chat_completion_model_name として指定します。
その他 default_options で指定できるのは以下のとおりです。

Langchain::LLM::OpenAI.new(
	api_key: ENV["OPENAI_API_KEY"],
	default_options: {
		chat_completion_mode_name: #{モデル名},
		n: #{n},
		temperature: #{temperature},
	}
)

ChatGPT のようにストリーミングで表示するには一手間必要になります。
chat メソッドはブロックを受け取ることができるので、チャンクごとに出力することで滑らかに表示させることが可能です。

response = ""
llm.chat(messages: messages) do | chunk |
  content = chunk.dig('delta', 'content') rescue nil
  next unless content
  print content
  response += content
end

https://github.com/patterns-ai-core/langchainrb/blob/e84785509a95ec2ac8236e5cf77f66987defb735/lib/langchain/llm/openai.rb#L113-L140

Prompt Management

プロンプトマネジメントはその名の通りプロンプト管理機能です。
JSON でプロンプトを管理することができるので、機能ごとにプロンプトだけを切り出すことが容易になります。
プロンプトマネジメントは LLM の API に依存していないので、プロンプトを扱うどのアプリでも取り入れやすい機能といえます。

基本の使い方。

prompt = Langchain::Prompt::PromptTemplate.new(template: "Tell me a {adjective} joke about {content}.", input_variables: ["adjective", "content"])
prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke about chickens."

プロンプトを JSON 形式で保存したり、JSON からロード可能

# jsonで保存
prompt.save(file_path: "spec/fixtures/prompt/prompt_template.json")

# jsonからロードする
prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/prompt_template.json")
prompt.input_variables # ["adjective", "content"]

json 以外にも yaml または yml がサポートされているようです。
https://github.com/patterns-ai-core/langchainrb/blob/e84785509a95ec2ac8236e5cf77f66987defb735/lib/langchain/prompt/loading.rb#L29-L42

フューショットラーニングのプロンプトも簡潔に書けます。

prompt = Langchain::Prompt::FewShotPromptTemplate.new(
  prefix: "足し算をしてください",
  suffix: "Input: {input}\nOutput:",
  example_prompt: Langchain::Prompt::PromptTemplate.new(
    input_variables: ["input", "output"],
    template: "Input: {input}\nOutput: {output}"
  ),
  examples: [
    { "input": "3+5", "output": "8" },
    { "input": "4+1", "output": "5" }
  ],
  input_variables: ["input"]
)

prompt.format(input: '100+300')
# "足し算をしてください\n\nInput: 3+5\nOutput: 8\n\nInput: 4+1\nOutput: 5\n\nInput: 3+5\nOutput:"

Output Parser

LLM のレスポンスを指定したスキーマで構造化することができる機能です。
OpenAI の API には json モードがあるので不要かもしれませんが、それ以外の LLM を使用する際に使えるかもしれません。

# プロンプト
json_schema = {
  type: "object",
  properties: {
    name: {
      type: "string",
      description: "Persons name"
    },
    age: {
      type: "number",
      description: "Persons age"
    },
    interests: {
      type: "array",
      items: {
        type: "object",
        properties: {
          interest: {
            type: "string",
            description: "A topic of interest"
          },
          levelOfInterest: {
            type: "number",
            description: "A value between 0 and 100 of how interested the person is in this interest"
          }
        },
        required: ["interest", "levelOfInterest"],
        additionalProperties: false
      },
      minItems: 1,
      maxItems: 3,
      description: "A list of the person's interests"
    }
  },
  required: ["name", "age", "interests"],
  additionalProperties: false
}
parser = Langchain::OutputParsers::StructuredOutputParser.from_json_schema(json_schema)
prompt = Langchain::Prompt::PromptTemplate.new(template: "Generate details of a fictional character.\n{format_instructions}\nCharacter description: {description}", input_variables: ["description", "format_instructions"])
prompt_text = prompt.format(description: "Korean chemistry student", format_instructions: parser.get_format_instructions)
# Generate details of a fictional character.
# You must format your output as a JSON value that adheres to a given "JSON Schema" instance.
# ...
# レスポンスをパースする
llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
llm_response = llm.chat(messages: [{role: "user", content: prompt_text}]).completion
parser.parse(llm_response)
# {
#   "name" => "Kim Ji-hyun",
#   "age" => 22,
#   "interests" => [
#     {
#       "interest" => "Organic Chemistry",
#       "levelOfInterest" => 85
#     },
#     ...
#   ]
# }

さいごに

最後まで読んでいただきありがとうございました。
本記事では基本のチャットに使う機能のみ紹介しました。
次回記事では Assistant と RAG の機能を触ってまとめてみようと思います。

Discussion