Summarization cookbook#

基本的な要約を超えてaidb.summarize_text_aggregate() を使用するための現実世界のレシピ。各レシピは、データに適応できる自己完結型のパターンです。

すべての例では、テーブルを含むsalesデータセットを使用します

レシピ#

1.カテゴリ別にまとめます#

使用する場合 任意のディメンション製品、顧客、地域、期間ごとにグループ化されたテキストの概要が必要です。

SELECT p.product_name,
       count(sn.note_id) AS note_count,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(my-llm)::json
       ) AS summary
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY p.product_name
ORDER BY note_count DESC;
  • チャンクしない場合、各グループのすべてのテキストは単一のLLM呼び出しとして送信されます。

  • 500ノート各平均〜250文字未満のグループの場合、チャンクなしが最速です。

  • 大規模なグループの場合、チャンクを追加してコンテキストウィンドウのオーバーフローを回避します Recipe 7 を参照してください。

2.ディール損失分析#

使用する場合 取引が失われている理由を、実行可能な理由とともに理解したいと考えています。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               prompt => Analyze these sales notes and answer: Why are we losing deals?
                         What are the top reasons for failure?
                         Provide specific, actionable insights.
           )::json
       ) AS loss_analysis
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status IN (lost, closed_lost)
GROUP BY p.product_name;
  • LLMが失敗パターンに焦点を当てるように、失われた取引のみWHERE status IN ('lost', 'closed_lost') にフィルターします。

  • プロンプトは具体的である必要があります。「top resume」は、「analyze」よりも良い結果をもたらします。

  • より多くの制約された出力については、 Recipe 8 を参照してください。

3.感情分析#

使用する場合 グループ化されたテキストから全体的な感情ポジティブ/ネガティブ/混合を測定したい。

- - Compare sentiment between won and lost deals
SELECT p.product_name,
       so.status AS deal_outcome,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               prompt => Analyze the overall sentiment of these entries. Provide:
                         1) Overall sentiment (positive/negative/neutral/mixed),
                         2) Key positive themes,
                         3) Key negative themes,
                         4) Notable concerns if any.
           )::json
       ) AS sentiment
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status IN (won, lost)
GROUP BY p.product_name, so.status
ORDER BY p.product_name, so.status;
  • 獲得した感情と失われた感情を並べて比較すると、成功した取引の違いが明らかになります。

  • プロンプトの番号付きフォーマットは、構造化された出力をガイドしますが、 LLMの遵守は規模で異なります 既知の制限 を参照してください。

4.重要なインサイトの抽出#

使用する場合 多くのエントリにわたって最も重要な所見の簡潔なリストが必要です。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               prompt => Extract the top 5 key insights from these entries as a numbered list.
                         Focus on actionable findings, patterns, and important trends.
           )::json
       ) AS insights
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY p.product_name
ORDER BY count(sn.note_id) DESC
LIMIT 5;
  • プロンプトの「トップN」と「番号付きリスト」は、出力長をコンストレインします。

  • 中程度のグループサイズ50〜500ノートで最適に動作します。非常に大規模なグループでは、LLMが特定の洞察ではなく一般的な概要を生成する場合があります。

5.問題の分類#

使用する場合 問題またはテーマを事前定義されたバケットに分類する必要があります。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               prompt => Categorize these sales notes into the following categories:
                         Pricing Concern, Technical Issue, Competitive Loss,
                         Timing/Budget, Feature Gap, Relationship/Trust, Integration Challenge.
                         For each category provide the count and percentage of entries that match,
                         plus a brief description.
           )::json
       ) AS categories
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status IN (lost, closed_lost)
GROUP BY p.product_name;
  • プロンプトでカテゴリを明示的に定義します。LLMに選択させないでください。

  • カウント/パーセンテージを要求して、定量的な出力を取得します。

  • LLMは、要求されたフォーマットに完全に従わない場合があります。より信頼性の高いフォーマットするには、ラッパーファンクション Recipe 8 を使用します。

6.エグゼクティブサマリー リデュース戦略#

使用する場合 詳細な説明ではなく、短く圧縮された概要が必要です。ダッシュボードとレポートに適しています。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               aidb.chunk_text_config(100, 100, 10, words),
               NULL,
               reduce,
               3  -- reduction_factor: each chunk targets desired_length/3 words
           )::json
       ) AS executive_summary
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY p.product_name;

仕組みテキストは100ワードのチャンクに分割されます。各チャンクはdesired_length / reduction_factor ワード100/3 ≈ チャンクごとに33ワードをターゲットに要約されます。最終結果がdesired_length 内に収まるまで、要約は結合、再チャンク、および再要約されます。出力は約400〜900文字高圧縮です。

  • reduction_factor=3 は、各チャンクをそのサイズの約33%に圧縮します。 rf=2 〜50%まで。

  • Reduceでは常にワードベースのチャンクを使用します。charベースは失敗する場合があります 既知の制限 を参照してください。

  • 最小安全なチャンクサイズ50ワード。

7.詳細な概要追加戦略#

使用する場合 テキストのすべての部分の詳細を保持する包括的なカバレッジが必要です。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               aidb.chunk_text_config(200, 200, 20, words),
               NULL,
               append
           )::json
       ) AS detailed_summary
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY p.product_name;

仕組みテキストは、20のワードがオーバーラップする200ワードのチャンクに分割されます。各チャンクは独立して要約されます。概要が連結されます。出力は約5,000〜15,000文字冗長、詳細です。

  • 大きなチャンク200ワード = 少ないLLM呼び出し = 高速ですが、粒度が低くなります。

  • 小さなチャンク50ワード=より多くのLLM呼び出し=遅くなりますが、より詳細です。

  • 20ワードのオーバーラップにより、チャンク境界でコンテキストが失われません。

8.再利用可能な構成ラッパーファンクション#

使用する場合 制約された出力形式での一貫した反復可能な分析が必要です。

事前構築された構成を返すSQLファンクションを作成し、summarize_text_aggregate() に直接渡します。このパターンは、JSONを返すファンクション内のプロンプトロジックをラップするため、長いプロンプト文字列を繰り返すことなくGROUP BY でクリーンに使用できます。

焦点を絞った分析

CREATE OR REPLACE FUNCTION analyze_top_reasons_config(
    p_question TEXT,
    p_n        INTEGER DEFAULT 3,
    p_model    TEXT    DEFAULT my-llm
) RETURNS JSON AS $$
    SELECT aidb.summarize_text_config(
        p_model,
        prompt => format(
            Answer this question about the text: %s
            Give exactly %s reasons, each as a single sentence.
            No preamble, no extra commentary.,
            p_question, p_n
        )
    )::json;
$$ LANGUAGE sql;

- - Usage: pass the config function directly to the aggregate
SELECT product_name,
       aidb.summarize_text_aggregate(
           note_text,
           analyze_top_reasons_config(Why are customers churning?, 3)
       ) AS churn_reasons
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status = lost
GROUP BY product_name;

構造化された感情

CREATE OR REPLACE FUNCTION analyze_sentiment_config(
    p_model TEXT DEFAULT my-llm
) RETURNS JSON AS $$
    SELECT aidb.summarize_text_config(
        p_model,
        prompt => Analyze sentiment. Respond ONLY in this format:
OVERALL: [positive/negative/neutral/mixed]
SCORE: [1-10]
TOP_POSITIVE: [one sentence]
TOP_NEGATIVE: [one sentence]
TREND: [improving/declining/stable]
    )::json;
$$ LANGUAGE sql;

- - Usage
SELECT product_name,
       aidb.summarize_text_aggregate(note_text, analyze_sentiment_config()) AS sentiment
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY product_name;

問題カテゴライザー

CREATE OR REPLACE FUNCTION categorize_issues_config(
    p_categories TEXT[]  DEFAULT ARRAY[
        pricing, technical, competition,
        timing, budget, feature_gap, other],
    p_model      TEXT    DEFAULT my-llm
) RETURNS JSON AS $$
    SELECT aidb.summarize_text_config(
        p_model,
        prompt => format(
            Categorize the issues in this text into ONLY these categories: %s.
            For each category, give a count and one representative example.
            Format: CATEGORY: count - "example quote",
            array_to_string(p_categories, , )
        )
    )::json;
$$ LANGUAGE sql;

- - Usage
SELECT product_name,
       aidb.summarize_text_aggregate(note_text, categorize_issues_config()) AS categories
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status IN (lost, closed_lost)
GROUP BY product_name;
  • 構成ラッパー機能は、一貫したインターフェイスを提供し、クエリを変更せずにプロンプトを調整できます。

  • 戻り値の型はsummarize_text_aggregate のパラメーター型と一致するように、JSONB ではなくJSON である必要があります。

  • 「プリアンブルなし」、「のみ」、「このフォーマットで応答」は効果的なステアリングフレーズです。

9.要約前のテキストのスクラブ#

使用する場合 テキストデータには、LLMトークンを無駄にするフィラーワード、挨拶、署名、またはボイラープレートが含まれています。

クリーンアップファンクションを作成し、集計の前に適用します。

CREATE OR REPLACE FUNCTION clean_note_text(p_text TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN regexp_replace(
        regexp_replace(
            regexp_replace(
                regexp_replace(p_text,
                    -- Remove email signatures
                    E(Best regards|Sincerely|Thanks|Cheers),?\s *\n.* $,
                    , gi),
                -- Remove greeting lines
                E^(Hi|Hello|Dear|Hey)\s+[A-Za-z]+,?\s*\n,
                , gi),
            -- Remove filler phrases
            E\b(basically|essentially|actually|literally|you know|I mean|sort of|kind of)\b,
            , gi),
        -- Collapse whitespace
        E\s+,  , g);
END;
$$ LANGUAGE plpgsql IMMUTABLE;

- - Use it:
SELECT product_name,
       aidb.summarize_text_aggregate(
           clean_note_text(note_text),
           aidb.summarize_text_config(my-llm)::json
       ) AS summary
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY product_name;
  • IMMUTABLE PostgreSQLは、繰り返しの呼び出しを最適化します。

  • データの正規表現パターンたとえば、内部専門用語、標準ヘッダーなどをカスタマイズします。

  • 一般的な削減 トークンが15〜30%減少します。これは、チャンクが減り、処理が高速になることを意味します。

レシピの組み合わせ 完全な最適化スタック#

最大の速度と品質を得るには、スクラビング+フィルタリング+構成ラッパーを組み合わせます。

SELECT p.product_name,
       aidb.summarize_text_aggregate(
           clean_note_text(sn.note_text),
           analyze_top_reasons_config(Why are we losing these deals?, 3)
       ) AS loss_reasons
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
WHERE so.status IN (lost, closed_lost)
GROUP BY p.product_name;

このパイプライン 関連する行 失われた取引にフィルタリングします。 clean_note_text() を介して行ごとにフィラーテキストをストリップしますトークンが15〜30%減少します。クリーン化されたすべてのテキストを蓄積し、LLMに送信します。構成ラッパーを介して制約されたプロンプトを適用して、短く焦点を当てた出力を実現します。

パフォーマンスベンチマーク#

テストスイートからのすべてのデータは、AIDB 6.1.0でOllamaを介してgranite4:1b を使用して、salesデータセット18,503ノート、平均266文字/ノートに対して実行されます。

チャンクなしのパフォーマンス#

チャンキングがない場合、グループ内のすべてのテキストは単一のLLM呼び出しとして送信されます。高速ですが、モデルのコンテキストウィンドウによって制限されます。

Group size

Duration

Result size

Notes

77–84 notes

21–28s

1,100–2,500 chars

安全な範囲、完全なデータ処理

100 notes

28s

2,200 chars

約26Kの文字入力、十分なコンテキスト内

2,915 notes

98s

3,200 chars

131Kトークンを超えるコンテキストウィンドウ以下を参照してください

3,248 notes

91s

1,700 chars

部分的なデータのみが処理されました

チャンク戦略の比較 50ノート#

Config

Est. LLM calls

Duration

ms/call

Result chars

Status

No chunking

1

12s

11,947

1,443

PASS

words/50/append

32

152s

4,759

12,159

PASS

words/100/append

16

77s

4,832

7,021

PASS

words/200/append

8

48s

6,044

4,781

PASS

words/50/reduce rf=2

63

217s

3,452

545

PASS

words/100/reduce rf=2

31

102s

3,280

475

PASS

words/200/reduce rf=2

15

51s

3,367

767

PASS

words/50/reduce rf=3

50

170s

3,396

410

PASS

words/100/reduce rf=3

25

72s

2,897

867

PASS

words/200/reduce rf=3

12

44s

3,661

535

PASS

chars/200/append

40

246s

6,154

16,355

PASS

chars/400/append

20

137s

6,835

11,793

PASS

chars/800/append

10

66s

6,584

5,897

PASS

chars/200/reduce rf=2

81

971s

11,988

—

FAIL

chars/400/reduce rf=2

41

368s

8,967

441

PASS

chars/800/reduce rf=2

21

144s

6,842

850

PASS

戦略効率の概要#

Strategy

Avg LLM calls

Avg total time

Avg ms/call

Avg output

No chunking

1

12s

11,947

1,443 chars

Append

21

121s

5,868

9,668 chars

Reduce rf=2

34

176s

5,182

616 chars

Reduce rf=3

29

95s

3,318

604 chars

予想される規模#

Scenario

100 notes

500 notes

1,000 notes

No chunking

28s (1 call)

~60s (1 call)

~100s (1 call, partial data*)

words/100/append

~77s (16 calls)

~385s (80 calls)

~770s (160 calls)

words/100/reduce rf=3

~72s (25 calls)

~180s (62 calls)

~240s (125 calls)

  • 1,000以上のノートでのノーチャンクは、コンテキストウィンドウを超える場合があります。完全を期すためにチャンクを使用します。

推奨される構成#

Use case

Config

Why

Fastest possible (< 500 notes)

No chunking

Single LLM call, stays within context window

Fast + detailed

words/200/append

Fewest calls for append, good detail

Balanced detail

words/100/append

More granular, moderate speed

Executive summary

words/100/reduce rf=3

Compressed output, efficient reduce

Maximum compression

words/50/reduce rf=3

Smallest output, most LLM calls

一般的なガイダンス チャンクなしで開始します。グループが〜500ノートを超える場合にのみチャンクを追加します。常に、文字ベースよりもワードベースのチャンクを優先します。 Reduceの場合、rf=2 よりもrf=3 を優先します。同様の出力品質で呼び出しの合計数が少なくなります。

既知の制限#

コンテキストウィンドウのオーバーフローサイレント#

入力テキストの合計がモデルのコンテキストウィンドウを超えると、テキストはサイレントに切り捨てられます。エラーは発生しません。LLMは、適合するものを処理し、残りを無視します。

例えば granite4:1b には、131,072トークンのコンテキストウィンドウがあります。 3,248ノート806K文字、〜201Kトークンのグループは、これを53%超えます。結果は有効な概要ですが、データの一部にすぎません。

安全な制限チャンキングなし

Avg note size

Safe note count

Total tokens

250 chars

~500 notes

~33K tokens

500 chars

~250 notes

~33K tokens

1,000 chars

~125 notes

~33K tokens

回避策これらの制限を超える可能性のあるグループにはチャンクを使用します。チャンク化は、コンテキストウィンドウのサイズに関係なく、すべてのデータを処理します。

- - Safe for large groups:
aidb.summarize_text_config(
    my-llm,
    aidb.chunk_text_config(200, 200, 20, words),
    NULL,
    append
)::json

小さなチャンクでの障害の削減#

reduce ストラテジーでは、各要約ラウンドが入力よりも短い出力を生成する必要があります。チャンクサイズが小さい場合、LLMは入力よりも長い概要を生成することが多く、次の原因が発生します。

Text summarization failed to reduce size. The summarized output is larger than or equal to the input.
Consider adjusting the chunking parameters.

障害しきい値

Chunk type

Size

Result

chars/200/reduce

200 chars

Always fails

chars/400/reduce

400 chars

Unreliable

chars/800/reduce

800 chars

Reliable

words/50/reduce

50 words

Reliable

words/100/reduce

100 words

Reliable

words/200/reduce

200 words

Reliable

回避策 reduce でワードベースのチャンクを使用しますテストされたすべてのサイズで100の合格率。 charベースを使用する必要がある場合は、 >= 800 charsを使用します。

温度、max_tokens、およびシステムプロンプトの制御#

デフォルトでは、LLMはプロバイダーのデフォルト温度通常0.7〜1.0を使用します。これにより、実行ごとに出力に大きな分散が発生する可能性があります。 inference_config を使用して、コールごとにこれらのパラメーターを制御します。

- - Control temperature and max_tokens for more consistent, bounded output
SELECT p.product_name,
       aidb.summarize_text_aggregate(
           sn.note_text,
           aidb.summarize_text_config(
               my-llm,
               NULL,
               Summarize the key points,
               NULL,
               NULL,
               aidb.inference_config(
                   temperature => 0.3,
                   max_tokens  => 500
               )
           )::json
       ) AS summary
FROM sales_notes sn
JOIN sales_orders so ON sn.order_id = so.order_id
JOIN products p ON so.product_id = p.product_id
GROUP BY p.product_name;

- - Override the system prompt for non-summarization tasks
SELECT aidb.summarize_text(
    input   => Customer complained about slow response times and billing errors.,
    options => aidb.summarize_text_config(
        my-llm,
        NULL,
        NULL,
        NULL,
        NULL,
        aidb.inference_config(
            system_prompt => You are a customer support analyst. Classify issues by severity.,
            temperature   => 0.2
        )
    )::json
);

!!!ヒント 最良の結果を得るには、inference_config と制約されたプロンプトを組み合わせます。たとえば、 max_tokens => 200 を設定し、 'Give exactly 3 bullet points, max 1 sentence each.' のようなプロンプトを使用します。トークン制限はハードキャップとして機能し、プロンプトがフォーマットをガイドします。

エラーなしの空の結果#

場合によっては、LLMは処理後に空の文字列を返す場合があります。 AIDBは、これをエラーなしの成功結果として保存します。アプリケーションコードでlength(result) を常に確認してください。

SELECT product_name,
       CASE
           WHEN length(summary) = 0  THEN ERROR: Empty result
           WHEN length(summary) < 50 THEN WARNING: Suspiciously short
           ELSE summary
       END AS validated_summary
FROM (
    SELECT p.product_name,
           aidb.summarize_text_aggregate(
               sn.note_text,
               aidb.summarize_text_config(
                   my-llm,
                   prompt => Your prompt here
               )::json
           ) AS summary
    FROM sales_notes sn
    JOIN sales_orders so ON sn.order_id = so.order_id
    JOIN products p ON so.product_id = p.product_id
    GROUP BY p.product_name
) sub;

プロンプトアドヒアランスは大規模に低下する#

大量のテキスト1,000以上のノートがある場合、LLMはカスタムプロンプトに関係なく、要約動作がデフォルトになる傾向があります。構造化された出力要求番号付きリスト、カテゴリフォーマット、センチメントラベルは、フォローの信頼性が低くなります。

回避策 LIMIT 以上の積極的なフィルタを使用して入力ボリュームを減らします。 append でチャンクを使用するため、各チャンクはより小さな入力に対して独立してプロンプトを受信します。非常に明示的なフォーマット制約を指定したラッパーファンクションを使用します。要約する前にスクラブテキスト Recipe 9 を使用してノイズを削減します。

最適化パターン#

1.テキストのスクラブ#

要約する前に、つなぎ言葉、挨拶、および定型文を削除します。 Recipe 9 を参照してください。影響 トークンが15〜30%削減され、それに比例して処理が高速化します。

2.コンストレインされたプロンプト#

特定の短いフォーマットの出力を生成するようにLLMに指示すると、トークン生成時間各呼び出しの最も遅い部分が削減されます。

- - Slow (open-ended, LLM may generate 2,000+ chars):
Summarize these notes.

- - Fast (constrained, LLM generates ~200 chars):
Give exactly 3 bullet points, max 1 sentence each.

3.抽出プレフィルタリング#

非常に大規模なデータセットの場合、LLMに送信する前にSQLを使用して最も関連性の高いテキストにプレフィルタリングします。

- - Only send notes containing relevant keywords
SELECT aidb.summarize_text_aggregate(
    note_text,
    aidb.summarize_text_config(
        my-llm,
        prompt => What are the pricing concerns?
    )::json
)
FROM sales_notes
WHERE note_text ILIKE %price%
   OR note_text ILIKE %cost%
   OR note_text ILIKE %budget%
   OR note_text ILIKE %expensive%
GROUP BY product_id;

インパクト 処理する行が大幅に減少。 LLMは関連するテキストのみを認識するため、速度と品質の両方が向上します。

4.適切な戦略の選択#

             ┌─ < 500 notes ──────> No chunking (fastest)
             │
Group size ──┤
             │              ┌─ Need detail ──> words/200/append
             └─ >= 500 notes ──┤
                            └─ Need brief ───> words/100/reduce rf=3

5.パフォーマンスコストモデル#

支配的なコストはLLM呼び出し数です。入力サイズに応じて、各呼び出しには3〜12秒かかります。

Step

Time

Network overhead

~50ms

Prompt processing

~1–3s

Token generation

~2–8s

Response parsing

~10ms

Total per call

~3–12s

プロンプト処理は入力長に応じてスケーリングしますが、トークン生成は出力長に応じてスケーリングします。サマリーは入力量に関係なくほぼ固定サイズであるため、コンテキストウィンドウの制限までは、多数の小さな呼び出しよりも、より少なくて大きな呼び出しの方が効率的です。これが、ノーチャンクが小規模なグループに非常に効果的な理由です。呼び出しごとのテキストを最大化し、呼び出し数を最小限に抑えます。集計は処理前にすべての行を累計しているため、テキストは自然にバッチ化されます。