最近個人開発でRixieというAIエージェントオーケストレーション用のRuby gemを作ってgemとして公開しました。
Rixieを作った動機はシンプルで、 PythonのLangChainやLlamaIndexのようなエージェントフレームワークをRubyで使いたかったのですが、 既存の選択肢はRailsに依存していたり、機能が限定的だったりと自分のユースケースにフィットするものなかったのと、 単純に自分でAIエージェントの中身の理解のためにAIエージェントフレームワークを作ってみたかったので今回作ってみました。
Railsに依存しないスタンドアロンなgemとして、 セッション管理・ツール実行・複数のエージェント戦略・MCPサポートを一通り揃えています。
動作確認環境: Ruby 3.4 / rixie 0.1.0
本題
概念階層:Session → Task → Run → think
Rixieの実行モデルは4層の階層で整理されています。
Session # 会話全体を管理。複数のchatをまたいで履歴を保持
└── Task # 1つのゴールを担当。Strategyを持つ
└── Run × N # Agent#thinkを1回呼び出す単位
└── think (llm_call × N) # LLMを呼び、ツールを実行し、ループする
Session#chatを呼ぶたびに1つのTaskが生成され、TaskはStrategyに従ってRunを何回実行するかを決めます。RunはAgentにthinkを依頼し、Agentはツール呼び出しが完了するまでLLMとのループを回します。
セットアップと基本的な使い方
以下の通り、gem "openai"や機能によっては追加で必要なライブラリが必要なことはありますが、
基本的にはRubyの標準機能の範囲内で動くように作っています。
# Gemfile gem "rixie" gem "openai" # OpenAI / OpenAI互換エンドポイントを使う場合 gem "nokogiri" # Fetch・WebSearchツールを使う場合 gem "cli-ui" # CLIを使う場合
実際に使うときは以下のような感じで設定し、Rixie::Sessionを生成してRixie::Session#chatでLLMを呼び出します。
require "rixie" Rixie.configure do |config| config.default_provider = "openai" config.default_model = "gpt-4.1-mini" end session = Rixie::Session.new(instructions: "You are a helpful assistant.") puts session.chat("Rubyの良いところを教えてください") # => "Rubyの良いところは..."
OpenAI互換エンドポイント対応
BuiltinのプロバイダはOpenAIですが、register_providerでGitHub ModelsやOllamaなどのOpenAI互換エンドポイントを登録できます。
Rixie.configure do |config| # GitHub Models config.register_provider("github", adapter: :openai, base_url: "https://models.github.ai/inference", api_key: ENV["GITHUB_TOKEN"] ) # Ollama(ローカル) config.register_provider("ollama", adapter: :openai, base_url: "http://localhost:11434/v1", api_key: "ollama" ) end # GitHubModelsを使う場合 session = Rixie::Session.new( instructions: "You are a helpful assistant.", provider: "github", model: "openai/gpt-4.1-mini" )
Strategy(エージェント戦略)の切り替え
Session#chatのstrategy:引数で実行戦略を切り替えられます。
# Simple(デフォルト): 1つのAgentループで完結するタスク向け session.chat("今日の東京の天気は?") # PlanExecute: まず計画を立て、各ステップを順番に実行する複雑なタスク向け session.chat( "Ruby 3.xの新機能を調査してレポートにまとめて", strategy: Rixie::Strategy::PlanExecute.new ) # ReAct: Thought → Action → Observationのサイクルを明示するデバッグ・ステップ確認向け session.chat( "東京の人口を調べて2倍にした数を教えて", strategy: Rixie::Strategy::ReAct.new )
デフォルトでは上記の3つを用意していますが、run(task:, listener:) を実装したクラスを渡すだけで独自のストラテジーを作れます。Run を複数作って task.runs に積み、run.execute(listener:) を呼ぶだけです。
以下は「同じ入力を2つのエージェントに投げて最初に返った方を採用する」例です(シンプルな実装例として直列で書いています)。
class BestOfTwoStrategy def run(task:, listener:) candidates = 2.times.map do run = Rixie::Run.new( user_input: task.user_input, agent: task.agent, context: task.context ) task.runs << run run.execute(listener:) run.output end # 長い方を「より詳細な回答」として採用する例 candidates.max_by(&:length) end end session.chat("Rubyの魅力を教えて", strategy: BestOfTwoStrategy.new)
組み込みツール群
以下のような組み込みツールを用意してるのでWebサーチのようなRAG的な機能や、HILのような機能も実装することができます。
| ツール | 説明 |
|---|---|
Tool::WebSearch |
DuckDuckGo Liteで検索(nokogiriが必要) |
Tool::Fetch |
URLを取得してテキスト抽出(SSRF対策あり) |
Tool::WikipediaSearch |
Wikipedia検索(言語指定可) |
Tool::Calculator |
四則演算・べき乗など。手書きパーサでevalを使わない実装 |
Tool::CurrentTime |
現在時刻をISO 8601形式で返す |
Tool::FileRead |
ファイル読み込み(root_dirサンドボックス内のみ) |
Tool::FileList |
globパターンでファイル一覧を取得 |
Tool::FileSearch |
regexでファイル横断grep |
Tool::HumanInput |
エージェントからユーザーへの質問(Human-in-the-loop) |
session = Rixie::Session.new( instructions: "You are a research assistant.", tools: [ Rixie::Tool::WebSearch, Rixie::Tool::Calculator, Rixie::Tool::FileRead.with(root_dir: "/path/to/project") ] )
MCPサポート
HTTPベースだけですがMCPもサポートしています。
require "rixie/mcp" mcp = Rixie::MCP::Http::Client.new(url: "http://localhost:8000/mcp") session = Rixie::Session.new( instructions: "You are a helpful assistant.", tools: mcp.tools + [Rixie::Tool::Calculator] )
ストリーミング
Session#liveでトークンをリアルタイムに受け取れます。返り値はEnumeratorで、パターンマッチでイベントを振り分けます。
session.live("最新のRubyリリースを調べて").each do |envelope| case envelope.event in Rixie::Event::Token[delta:] print delta; $stdout.flush in Rixie::Event::ToolCallStart[tool_call:] puts "\n[#{tool_call.name} を呼び出し中...]" in Rixie::Event::Finished[content:] puts "\n#{content}" else end end
マルチエージェントオーケストレーション
SessionをRixie::Toolとしてラップすることで、エージェントを組み合わせ、コンテキスト節約や専門的なエージェントに作業を委譲させたりといったマルチエージェントオーケストレーションを構築できます。
(ツールベースの非決定論的なマルチエージェントオーケストレーションではなく決定論的なワークフローのようなものを組みたい場合には前述のStrategyを利用することで実現できます。)
research_agent = Rixie::Session.new(instructions: "You are a research specialist.") write_agent = Rixie::Session.new(instructions: "You are a technical writer.") orchestrator = Rixie::Session.new( instructions: "研究と執筆を調整してレポートを作成してください。", tools: [ Rixie::Tool.new( name: "research", description: "トピックを調査して調査結果を返す", input_schema: {type: "object", properties: {query: {type: "string"}}, required: ["query"]}, call: ->(args) { research_agent.chat(args["query"]) } ), Rixie::Tool.new( name: "write", description: "トピックに基づいてレポートを書く", input_schema: {type: "object", properties: {topic: {type: "string"}}, required: ["topic"]}, call: ->(args) { write_agent.chat(args["topic"]) } ) ], parallel_tool_calls: true )
おわりに
まだv0.1.0で荒削りな部分もありますが、個人サービスで利用している範囲ではありますが、RailsなしのRubyスクリプトやCLIツールからAIエージェントを動かしたいときには使いやすいなぁというのと、自身でAIエージェント系のフレームワークを自作してみて理解が深まったので作ってみてよかったです💎



