2023-06-27 11:26:33 -04:00
# frozen_string_literal: true
module DiscourseAi
module Summarization
module Strategies
class FoldContent < :: Summarization :: Base
def initialize ( completion_model )
@completion_model = completion_model
end
attr_reader :completion_model
delegate :correctly_configured? ,
:display_name ,
:configuration_hint ,
:model ,
to : :completion_model
2023-11-23 10:58:54 -05:00
def summarize ( content , user , & on_partial_blk )
2023-06-27 11:26:33 -04:00
opts = content . except ( :contents )
2023-11-28 23:17:46 -05:00
llm = DiscourseAi :: Completions :: Llm . proxy ( completion_model . model )
2023-11-23 10:58:54 -05:00
2023-12-06 17:00:24 -05:00
initial_chunks =
rebalance_chunks (
llm . tokenizer ,
content [ :contents ] . map { | c | { ids : [ c [ :id ] ] , summary : format_content_item ( c ) } } ,
)
# Special case where we can do all the summarization in one pass.
if initial_chunks . length == 1
2023-08-11 14:08:54 -04:00
{
2023-12-06 17:00:24 -05:00
summary :
summarize_single ( llm , initial_chunks . first [ :summary ] , user , opts , & on_partial_blk ) ,
2023-08-11 14:08:54 -04:00
chunks : [ ] ,
}
2023-07-13 16:05:41 -04:00
else
2023-12-06 17:00:24 -05:00
summarize_chunks ( llm , initial_chunks , user , opts , & on_partial_blk )
end
end
2023-07-13 16:05:41 -04:00
2023-12-06 17:00:24 -05:00
private
def summarize_chunks ( llm , chunks , user , opts , & on_partial_blk )
# Safely assume we always have more than one chunk.
summarized_chunks = summarize_in_chunks ( llm , chunks , user , opts )
total_summaries_size =
llm . tokenizer . size ( summarized_chunks . map { | s | s [ :summary ] . to_s } . join )
if total_summaries_size < completion_model . available_tokens
# Chunks are small enough, we can concatenate them.
2023-08-11 14:08:54 -04:00
{
2023-11-23 10:58:54 -05:00
summary :
concatenate_summaries (
llm ,
2023-12-06 17:00:24 -05:00
summarized_chunks . map { | s | s [ :summary ] } ,
2023-11-23 10:58:54 -05:00
user ,
& on_partial_blk
) ,
2023-12-06 17:00:24 -05:00
chunks : summarized_chunks ,
2023-08-11 14:08:54 -04:00
}
2023-12-06 17:00:24 -05:00
else
# We have summarized chunks but we can't concatenate them yet. Split them into smaller summaries and summarize again.
rebalanced_chunks = rebalance_chunks ( llm . tokenizer , summarized_chunks )
summarize_chunks ( llm , rebalanced_chunks , user , opts , & on_partial_blk )
2023-07-13 16:05:41 -04:00
end
end
2023-11-23 10:58:54 -05:00
def format_content_item ( item )
" ( #{ item [ :id ] } #{ item [ :poster ] } said: #{ item [ :text ] } "
end
2023-12-06 17:00:24 -05:00
def rebalance_chunks ( tokenizer , chunks )
2023-07-13 16:05:41 -04:00
section = { ids : [ ] , summary : " " }
chunks =
2023-12-06 17:00:24 -05:00
chunks . reduce ( [ ] ) do | sections , chunk |
2023-11-23 10:58:54 -05:00
if tokenizer . can_expand_tokens? (
2023-07-13 16:05:41 -04:00
section [ :summary ] ,
2023-12-06 17:00:24 -05:00
chunk [ :summary ] ,
2023-07-13 16:05:41 -04:00
completion_model . available_tokens ,
)
2023-12-06 17:00:24 -05:00
section [ :summary ] += chunk [ :summary ]
section [ :ids ] = section [ :ids ] . concat ( chunk [ :ids ] )
2023-07-13 16:05:41 -04:00
else
sections << section
2023-12-06 17:00:24 -05:00
section = chunk
2023-07-13 16:05:41 -04:00
end
sections
end
chunks << section if section [ :summary ] . present?
chunks
2023-06-27 11:26:33 -04:00
end
2023-11-23 10:58:54 -05:00
def summarize_single ( llm , text , user , opts , & on_partial_blk )
prompt = summarization_prompt ( text , opts )
llm . completion! ( prompt , user , & on_partial_blk )
end
def summarize_in_chunks ( llm , chunks , user , opts )
chunks . map do | chunk |
prompt = summarization_prompt ( chunk [ :summary ] , opts )
prompt [ :post_insts ] = " Don't use more than 400 words for the summary. "
chunk [ :summary ] = llm . completion! ( prompt , user )
chunk
end
end
def concatenate_summaries ( llm , summaries , user , & on_partial_blk )
2023-12-06 17:00:24 -05:00
prompt = { }
2023-11-23 10:58:54 -05:00
prompt [ :insts ] = << ~ TEXT
2023-12-06 17:00:24 -05:00
You are a summarization bot that effectively concatenates disjoint summaries , creating a cohesive narrative .
The narrative you create is in the form of one or multiple paragraphs .
Your reply MUST BE a single concatenated summary using the summaries I ' ll provide to you .
I 'm NOT interested in anything other than the concatenated summary, don' t include additional text or comments .
You understand and generate Discourse forum Markdown .
You format the response , including links , using Markdown .
TEXT
prompt [ :input ] = << ~ TEXT
THESE are the summaries , each one separated by a newline , all of them inside < input > < / input> XML tags:
< input >
#{summaries.join("\n")}
< / input>
2023-11-23 10:58:54 -05:00
TEXT
llm . completion! ( prompt , user , & on_partial_blk )
end
def summarization_prompt ( input , opts )
insts = << ~ TEXT
2023-11-23 14:33:37 -05:00
You are a summarization bot that effectively summarize any text
2023-12-06 17:00:24 -05:00
Your reply MUST BE a summarized version of the posts I provided , using the first language you detect .
I 'm NOT interested in anything other than the summary, don' t include additional text or comments .
2023-11-23 10:58:54 -05:00
You understand and generate Discourse forum Markdown .
You format the response , including links , using Markdown .
2023-11-23 14:33:37 -05:00
Your summaries are always a cohesive narrative in the form of one or multiple paragraphs .
2023-11-23 10:58:54 -05:00
TEXT
insts += << ~ TEXT if opts [ :resource_path ]
2023-11-23 14:33:37 -05:00
Each post is formatted as " <POST_NUMBER>) <USERNAME> <MESSAGE> "
Try generating links as well the format is #{opts[:resource_path]}/<POST_NUMBER>
For example , a link to the 3 rd post in the topic would be [ post 3 ] ( #{opts[:resource_path]}/3)
2023-11-23 10:58:54 -05:00
TEXT
prompt = { insts : insts , input : << ~ TEXT }
2023-12-15 12:32:01 -05:00
#{opts[:content_title].present? ? "The discussion title is: " + opts[:content_title] + ".\n" : ""}
2023-12-06 17:00:24 -05:00
Here are the posts , inside < input > < / input> XML tags:
2023-11-23 10:58:54 -05:00
< input >
#{input}
< / input>
TEXT
if opts [ :resource_path ]
prompt [ :examples ] = [
[
2023-11-23 14:33:37 -05:00
" <input>1) user1 said: I love Mondays 2) user2 said: I hate Mondays</input> " ,
2023-11-23 10:58:54 -05:00
" Two users are sharing their feelings toward Mondays. [user1]( #{ opts [ :resource_path ] } /1) hates them, while [user2]( #{ opts [ :resource_path ] } /2) loves them. " ,
] ,
[
" <input>3) usuario1: Amo los lunes 6) usuario2: Odio los lunes</input> " ,
" Dos usuarios charlan sobre los lunes. [usuario1]( #{ opts [ :resource_path ] } /3) dice que los ama, mientras que [usuario2]( #{ opts [ :resource_path ] } /2) los odia. " ,
] ,
]
end
prompt
end
2023-06-27 11:26:33 -04:00
end
end
end
end