Action Text 埋め込み添付ファイルの抽出を RubyLLM に対応

crmne/ruby_llm

Action Text のリッチテキストは HTML を返すだけでなく、埋め込まれた Active Storage の添付ファイルを保持しますが、これまで RubyLLM::Content への抽出では除外されていました。結果として LLM へ送信するテキストに不要なタグが混入し、添付ファイルも失われていました。今回の PR はその欠損を解消し、埋め込み添付ファイルを Content の添付として取得できるようにします。

背景

Action Text を has_rich_text :content で利用したモデルでは、message.contentActionText::RichText オブジェクトとなり、to_plain_text が利用できないケースがありました。従来の extract_content は HTML 文字列をそのまま返すか、テキスト属性のみを対象にしていたため、埋め込み画像やファイルが無視されていました。#365 での議論を受け、埋め込み添付の抽出 を共通化する必要が生じました。

技術的な変更

lib/ruby_llm/active_record/attachment_helpers.rb に、Action Text 専用のヘルパーメソッドが追加されました。plain_text_content はオブジェクトが to_plain_text を実装していれば呼び出し、実装が無い場合はそのまま返します。action_text_attachment_sourcesbody.attachables から個々の添付オブジェクトを取得し、action_text_attachable_sourcesActiveStorage::Blob へ変換します。さらに、content_attachments?add_content_attachments が、Action Text と従来の Active Storage 両方の添付を統合的に処理できるようにしています。

def plain_text_content(content_value)
  return content_value.to_plain_text if content_value.respond_to?(:to_plain_text)
  content_value
end

def action_text_attachment_sources(content_value)
  return [] unless content_value.respond_to?(:body)
  body = content_value.body
  return [] unless body.respond_to?(:attachables)
  body.attachables.flat_map { |attachable| action_text_attachable_sources(attachable) }.compact
end

lib/ruby_llm/active_record/message_methods.rbextract_content が新ヘルパーを利用するように改修されました。まず plain_text_content でテキスト化し、次に action_text_attachment_sources で添付リストを取得します。content_attachments? が true であれば RubyLLM::Content を生成し、add_content_attachments で取得した添付をすべて追加します。これにより、HTML タグは除去されつつ、画像やファイルが正しく保持されます。

content_text = plain_text_content(content_value)
action_text_attachments = action_text_attachment_sources(content_value)
return content_text unless content_attachments?(action_text_attachments)
RubyLLM::Content.new(content_text).tap do |content_obj|
  @_tempfiles = []
  add_content_attachments(content_obj, action_text_attachments)
end

テストのために spec/dummy/db/migrate/20260506120000_create_action_text_tables.action_text.rb が追加され、action_text_rich_texts テーブルを作成します。Rails 標準の Action Text マイグレーションをコピーしただけで、テスト環境でリッチテキストモデルが動作できるようにしています。

class CreateActionTextTables < ActiveRecord::Migration[7.0]
  def change
    create_table :action_text_rich_texts do |t|
      t.string :name, null: false
      t.text :body
      t.references :record, null: false, polymorphic: true, index: false
      t.timestamps
      t.index %i[record_type record_id name], name: :index_action_text_rich_texts_uniqueness, unique: true
    end
  end
end

同様に spec/dummy/db/schema.rb へテーブル定義が追記され、テスト実行時にスキーマが正しく反映されます。これらの変更はライブラリ本体には影響せず、テスト専用のサンドボックスとして機能します。

設計判断

Action Text 用のロジックは既存の AttachmentHelpers に統合された点が重要です。新規モジュールを導入せず、plain_text_content から add_content_attachments までのフローを拡張したことで、コードベースの一貫性と再利用性が維持されています。外部依存は Active StorageAction Text のみで、他の部分への侵入は最小限に抑えられました。

extract_content の公開インターフェイスは変更されていません。呼び出し側は引き続き RubyLLM::Content オブジェクトを受け取り、添付ファイルが自動的に内部に組み込まれるだけです。これにより既存ユーザーはアップグレード時にコード修正を不要とし、後方互換性が保たれます。

テスト用マイグレーションは dummy アプリ に限って提供され、実稼働環境のマイグレーションに混入しません。Action Text が必須でないプロジェクトでも本体ライブラリは影響を受けず、オプション的に機能を利用できる設計となっています。

まとめ

本 PR は Action Text の埋め込み添付ファイルを RubyLLM::Content に安全に取り込む機構を追加し、テキスト抽出の正確性と添付情報の完全性を向上させました。既存 API の変更はなく、テスト環境専用のマイグレーションだけが新たに導入されるため、ユーザーへの影響は最小限です。結果として LLM への入力がクリーンかつリッチになることが期待されます。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
893bcc00

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

記事はリード文(総論)→背景・技術的変更・設計判断(各論)→まとめ(結論)の3部構成が整っており、必須要素がすべて揃っています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは ` ```ruby:ファイルパス` 形式で正しく記述され、PRリンクも `[#760](URL)` で正しい形式です。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

技術的な前提があるエンジニア向けに書かれており、初心者向けの余計な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論のパラグラフで構成され、トピックセンテンスで始まり、1段落1トピック、段落は6文以内で空行で区切られています。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

`attachment_helpers.rb` のコードブロックは Diff に含まれる新規メソッド(action_text_attachable_sources, content_attachments?, active_storage_attachments?, add_content_attachments など)を省略しています。記事本文ではこれらのメソッドが言及されていますが、コード例が抜けているため完全な照合ができません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Action Text、Active Storage、RubyLLM::Content 等の用語が正しく使用され、PR 内容と一致しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明は PR の要件・変更点と整合しており、誤った情報は見当たりません。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の主張はすべて PR のタイトル・説明・Diff で裏付け可能です。数値的根拠や外部知識の捏造はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #760 が正しく記載されており、他の数値・固有名詞の誤りはありません。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の「Support Action Text embedded attachments」の趣旨と一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR に記載されていない外部情報(LTS、リリース日等)は一切含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。