Open15

langchainrbのコードを読んでいく

uehara1414uehara1414

手始めに Langchain::LLM::OpenAI を見ていく。

  • embed
  • complete
  • chat
  • summarize
    が存在する。どれも継承元の LLM::OpenAI::Base にて宣言されているもの。
    chatをまず見ていく。
chat(prompt: "", messages: [], context: "", examples: [], **options)

prompt: はユーザーの質問。文字列。

OpenAI::Client.chatに渡されるパラメータは以下をデフォルトとし、optionsで指定されたもので上書きし、プロンプトはprompt, messages, context, examplesを元に構築する形。

    DEFAULTS = {
      temperature: 0.0,
      completion_model_name: "gpt-3.5-turbo",
      chat_completion_model_name: "gpt-3.5-turbo",
      embeddings_model_name: "text-embedding-ada-002",
      dimension: 1536
    }.freeze

また、functionsが事前に指定されていれば Function calling が設定される。

uehara1414uehara1414

.chatのプロンプトの構築方法

指定されたcontextが存在する場合、messagesおよびexamplesからrole: systemのメッセージを削除。
その上で、historyの最初にcontextをシステムプロンプトとして設定する。

historyの最後にpromptをrole: userで追加する。もしpromptが存在せず、historyの最後が{role: user}であれば、historyの最後のメッセージのコンテンツにpromptを付加。

例:
ruby

openai.chat(prompt: "factory_botはいつリリースされましたか?", 
context: "質問に答えてください", 
messages: [{role: "user", content: "魚は英語で?"}, {role: "assistant", content: "fish"}],
examples: [{role: "user", content: "Ruby on Railsはいつリリースされましたか?"}, {role: "assistant", content: "2004"}],
temperature: 0.3))

上記のコードは内部で以下のようにOpenAI APIを呼び出すことになる。

openai.chat(parameters: {
      temperature: 0.3, 
      completion_model_name: "gpt-3.5-turbo",
      chat_completion_model_name: "gpt-3.5-turbo",
      embeddings_model_name: "text-embedding-ada-002",
      dimension: 1536,
      messages: [
        {role: 'system', content: '質問に答えてください'},
        {role: "user", content: "Ruby on Railsはいつリリースされましたか?"}, 
        {role: "assistant", content: "2004"},
        {role: "user", content: "魚は英語で?"},
        {role: "assistant", content: "fish"},
        {role: 'user', content: 'factory_botはいつリリースされましたか?'}
      ]
})
uehara1414uehara1414

summarize(text:)

{role: 'user', content: "Write a concise summary of the following: #{text}"}
で opanai.chat に問い合わせ、結果をstringで返すだけ。あまり捻りはない。

ただ、ここで Langchain::Prompt が利用されている。

      prompt_template = Langchain::Prompt.load_from_path(
        file_path: Langchain.root.join("langchain/llm/prompts/summarize_template.yaml")
      )
      prompt = prompt_template.format(text: text)

次はこのLangchain::Promptの実装を見ていく

uehara1414uehara1414

プロンプトテンプレート

  1. Langchain::Prompt::PromptTemplate
  2. Langchain::Prompt::FewShotPromptTemplate

この2種がある模様。

uehara1414uehara1414

Langchain::Prompt::PromptTemplate の機能

基本的にはこの機能のみ。

prompt = Langchain::Prompt::PromptTemplate.new(
  template: "Tell me a {adjective} joke.", 
  input_variables: ["adjective"])

prompt.format(adjective: "funny") # "Tell me a funny joke."

{}でキーワードを囲った形式のテンプレートを作成し、それにキーワードを渡して完成した文を取得できる。

uehara1414uehara1414

Langchain::Prompt::FewShotPromptTemplate

  1. 前置き
  2. 複数の例示
  3. 最後の文
    という形式のプロンプトを作成したいときに利用できる
prompt = Langchain::Prompt::FewShotPromptTemplate.new(
  prefix: "Write antonyms for the following words.",
  suffix: "Input: {adjective}\nOutput:",
  example_prompt: Langchain::Prompt::PromptTemplate.new(
    input_variables: ["input", "output"],
    template: "Input: {input}\nOutput: {output}"
  ),
  examples: [
    {input: "happy", output: "sad"},
    {input: "tall", output: "short"}
  ],
  input_variables: ["adjective"]
)
prompt.format(adjective: "good") # ↓になる
# Write antonyms for the following words.
# 
# Input: happy
# Output: sad
# 
# Input: tall
# Output: short
# 
# Input: good
# Output:
uehara1414uehara1414

examples/pdf_store_and_query_with_chroma.rb

読んでいく。
dotenvの読み込みが無かったのでプルリクを出しておく。

https://github.com/andreibondarev/langchainrb/pull/329

最初のコード

chroma = Langchain::Vectorsearch::Chroma.new(
  url: ENV["CHROMA_URL"],
  index_name: "documents",
  llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)

chromaのインスタンスを作成する。chromaのサーバーは別で立てておく必要がある。chromaをcloneしてdockerで立てる

git clone git@github.com:chroma-core/chroma.git
cd chroma
docker compose up

デフォルトでは8000番で立つ。
.envのCHROMA_URLを修正する

CHROMA_URL=http://localhost:8000

これでここは通る。

uehara1414uehara1414
chroma.create_default_schema

このコードは2度目はエラーを返す

Collection documents already exists. (Chroma::InvalidRequestError)

ので、そのときは無視するように修正しておく

begin
  chroma.create_default_schema
rescue Chroma::InvalidRequestError
  # Ignore this error, it just means the schema already exists
end
uehara1414uehara1414
# Set up an array of PDF and TXT documents
docs = [
  Langchain.root.join("/docs/document.pdf"),
  Langchain.root.join("/docs/document.txt"),
  Langchain.root.join("/docs/document.docx")
]

# Add data to the index. Weaviate will use OpenAI to generate embeddings behind the scene.
chroma.add_data(
  paths: docs
)

これもそのままでは動かない。こんなファイルはプロジェクトに存在しない。
Langchain.rootは langchaing/lib/ を指す Pathlib を返すだけ。

add_dataの実装は以下のようになっている。

def add_data(paths:)
  raise ArgumentError, "Paths must be provided" if Array(paths).empty?

  texts = Array(paths)
    .flatten
    .map do |path|
      data = Langchain::Loader.new(path)&.load&.chunks
      data.map { |chunk| chunk[:text] }
    end

  texts.flatten!

  add_texts(texts: texts)
end

そんで Loader.load の実装を見るとこれは url でもいいようなので、適当なネット上のファイルのURLを渡すように書き換える。
docxもいけるようなので、このリポジトリのテスト用fixtureのtxtのファイルのURLを貼る

# Set up an array of PDF and TXT documents
docs = [
  "https://raw.githubusercontent.com/andreibondarev/langchainrb/main/spec/fixtures/loaders/example.txt",
]

lib/langchain/processors/*.rb にある形式には対応していそう

ls lib/langchain/processors
base.rb csv.rb   docx.rb  html.rb  json.rb  jsonl.rb pdf.rb   text.rb  xlsx.rb

だが、これだとadd_textsが通らない。どうするか。

uehara1414uehara1414

Gemfile.lockにあるchroma-dbのバージョン(0.3.0)だと最新のchroma dbで動かなそうな感じだ。

chroma-db (0.3.0)

Requirements
Ruby 2.7.8 or newer
Chroma Database 0.3.25 or later running as a client/server model.
For Chroma database 0.3.22 or older, please use version 0.3.0 of this gem.

https://github.com/mariochavez/chroma

またプルリクを投げるか。

uehara1414uehara1414

とりあえず、これで最後までエラーなく実行できるようになる。

res = chroma.ask(
  question: "What is Lorem Ipsum?"
)

puts res
# Lorem Ipsum is a placeholder text that is commonly used in the printing and typesetting industry. It has been used since the 1500s as a standard dummy text for testing and demonstrating the visual effects of different fonts, layouts, and designs.

初期状態だと何も出力されないので、putsするようにする。

uehara1414uehara1414

chroma.add_data では

  1. 受け取ったURLかPathを拡張子やContent-Typeで対応するLoaderに渡す
  2. Loaderでデータをパースしてテキストに変換する
  3. テキストに対してllm(このexampleではOpenAI)のembedを行う
  4. chromaにembeddingを登録する

といったことを行なっている。