Open6

RubyでOpenAI APIで遊ぶ boxcars ActiveRecord編 #RubyKaigi #RubyKaigi2023

yuisekiyuiseki

Gemfileを更新する

source "https://rubygems.org"

gem 'boxcars'
gem 'dotenv'
gem 'hnswlib'
gem 'sqlite3'
gem 'activerecord'
bundle
yuisekiyuiseki

まずはサンプル通りにやってみる

helpdesk_sample.rb をプロジェクトのルートディレクトリにコピペして配置

ar.rb というファイルを作って、以下の内容にする

ar.rb
require "dotenv/load"
require "boxcars"
require 'active_record'
require './helpdesk_sample'

boxcar = Boxcars::SQL.new
boxcar.run "How many tickets are there?"
boxcar.run "How many comments do we have on tickets with a status of 1?"

これが期待通りに動くことを確認しておく

bundle exec ruby ar.rb

出力

-- create_table("users", {:force=>:cascade})
   -> 0.0191s
-- create_table("comments", {:force=>:cascade})
   -> 0.0019s
-- create_table("tickets", {:force=>:cascade})
   -> 0.0032s
> Entering Database#run
How many tickets are there?
SELECT COUNT(*) FROM tickets;
{"status":"ok","answer":3,"explanation":"Answer: 3","code":"SELECT COUNT(*) FROM tickets;"}
< Exiting Database#run
> Entering Database#run
How many comments do we have on tickets with a status of 1?
SELECT COUNT(*) FROM comments c JOIN tickets t ON c.ticket_id = t.id WHERE t.status = 1
{"status":"ok","answer":0,"explanation":"Answer: 0","code":"SELECT COUNT(*) FROM comments c JOIN tickets t ON c.ticket_id = t.id WHERE t.status = 1"}
< Exiting Database#run

動いてそうだ!

yuisekiyuiseki

boxcarsのサンプルに書かれているcarとかtrainってなんのことだ?と思ったら、LangChainにおけるToolやAgentのことのようだ。
carがToolにあたり、trainがAgentにあたる。

yuisekiyuiseki

サンプルのとおりに複数のcarでtrainを作ってそれを呼び出してみる

my_first_train.rb
require "dotenv/load"
require "boxcars"
require 'active_record'
require './helpdesk_sample'

calc = Boxcars::Calculator.new
helpdesk = Boxcars::ActiveRecord.new(name: 'helpdesk', models: [Ticket, User, Comment])

train = Boxcars.train.new(boxcars: [calc, helpdesk])
train.run "the number of open helpdesk tickets that John commented on times 2 pi?"

実行してみる

bundle exec ruby my_first_train.rb

出力

-- create_table("users", {:force=>:cascade})
   -> 0.0147s
-- create_table("comments", {:force=>:cascade})
   -> 0.0028s
-- create_table("tickets", {:force=>:cascade})
   -> 0.0021s
> Entering Zero Shot#run
the number of open helpdesk tickets that John commented on times 2 pi?
Thought: We need to query the helpdesk database to find the number of open tickets that John commented on and then multiply it by 2 pi. 
> Entering helpdesk#run
\`\`\`
query: open tickets
filter: John commented
\`\`\`
Ticket.joins(:comments, :user).where(status: 0, comments: {user_id: User.find_by(name: 'John').id}).distinct
{"status":"ok","answer":[{"id":1,"title":"First ticket","user_id":1,"status":"open","body":"This is the first ticket","created_at":"2023-05-11T04:20:15.683Z","updated_at":"2023-05-11T04:20:15.683Z"}],"explanation":"Answer: [{\"id\":1,\"title\":\"First ticket\",\"user_id\":1,\"status\":\"open\",\"body\":\"This is the first ticket\",\"created_at\":\"2023-05-11T04:20:15.683Z\",\"updated_at\":\"2023-05-11T04:20:15.683Z\"}]","code":"Ticket.joins(:comments, :user).where(status: 0, comments: {user_id: User.find_by(name: 'John').id}).distinct"}
< Exiting helpdesk#run
Observation: [{"id"=>1, "title"=>"First ticket", "user_id"=>1, "status"=>"open", "body"=>"This is the first ticket", "created_at"=>"2023-05-11T04:20:15.683Z", "updated_at"=>"2023-05-11T04:20:15.683Z"}]
Thought: The query returned one open ticket that John commented on. We can now calculate the final answer.
> Entering Calculator#run
\`\`\`
1 * 2 * Math::PI
\`\`\`
RubyREPL: puts 1 * 2 * Math::PI
Answer: 6.283185307179586

{"status":"ok","answer":"6.283185307179586","explanation":"Answer: 6.283185307179586","code":"puts 1 * 2 * Math::PI"}
< Exiting Calculator#run
Observation: 6.283185307179586
The final answer is 6.283185307179586.

Final Answer: 6.283185307179586

Next Actions:
1. What is the status of the open ticket that John commented on?
2. How many tickets are currently open in the helpdesk?
3. Who is the user with the most closed tickets in the helpdesk?
< Exiting Zero Shot#run

ReActっぽい動きをしている!

yuisekiyuiseki

次にやってみたいこと

  • さらに複雑なSQLDBで試してみたい
  • 自分で独自のcarを定義したい