2023-06-27 12:26:33 -03:00
# frozen_string_literal: true
module DiscourseAi
module Summarization
module Strategies
2024-07-02 08:51:59 -07:00
class FoldContent < DiscourseAi :: Summarization :: Models :: Base
2023-06-27 12:26:33 -03:00
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 12:58:54 -03:00
def summarize ( content , user , & on_partial_blk )
2023-06-27 12:26:33 -03:00
opts = content . except ( :contents )
2024-01-29 16:04:25 -03:00
llm = DiscourseAi :: Completions :: Llm . proxy ( completion_model . model_name )
2023-11-23 12:58:54 -03:00
2024-07-03 18:10:31 -03:00
initial_chunks =
rebalance_chunks (
llm . tokenizer ,
content [ :contents ] . map { | c | { ids : [ c [ :id ] ] , summary : format_content_item ( c ) } } ,
)
2023-12-06 19:00:24 -03:00
2024-07-03 18:10:31 -03:00
# Special case where we can do all the summarization in one pass.
if initial_chunks . length == 1
{
summary :
summarize_single ( llm , initial_chunks . first [ :summary ] , user , opts , & on_partial_blk ) ,
chunks : [ ] ,
}
else
summarize_chunks ( llm , initial_chunks , user , opts , & on_partial_blk )
end
2023-12-06 19:00:24 -03:00
end
2023-07-13 17:05:41 -03:00
2023-12-06 19:00:24 -03:00
private
2024-07-03 18:10:31 -03:00
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.
{
summary :
concatenate_summaries (
llm ,
summarized_chunks . map { | s | s [ :summary ] } ,
user ,
& on_partial_blk
) ,
chunks : summarized_chunks ,
}
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 )
end
end
2023-11-23 12:58:54 -03:00
def format_content_item ( item )
" ( #{ item [ :id ] } #{ item [ :poster ] } said: #{ item [ :text ] } "
end
2024-07-03 18:10:31 -03:00
def rebalance_chunks ( tokenizer , chunks )
section = { ids : [ ] , summary : " " }
chunks =
chunks . reduce ( [ ] ) do | sections , chunk |
if tokenizer . can_expand_tokens? (
section [ :summary ] ,
chunk [ :summary ] ,
completion_model . available_tokens ,
)
section [ :summary ] += chunk [ :summary ]
section [ :ids ] = section [ :ids ] . concat ( chunk [ :ids ] )
else
sections << section
section = chunk
end
sections
end
chunks << section if section [ :summary ] . present?
chunks
end
2023-11-23 12:58:54 -03:00
def summarize_single ( llm , text , user , opts , & on_partial_blk )
prompt = summarization_prompt ( text , opts )
2024-05-14 13:28:46 +10:00
llm . generate ( prompt , user : user , feature_name : " summarize " , & on_partial_blk )
2023-11-23 12:58:54 -03:00
end
2024-07-03 18:10:31 -03:00
def summarize_in_chunks ( llm , chunks , user , opts )
chunks . map do | chunk |
prompt = summarization_prompt ( chunk [ :summary ] , opts )
chunk [ :summary ] = llm . generate (
prompt ,
user : user ,
max_tokens : 300 ,
feature_name : " summarize " ,
)
chunk
end
end
def concatenate_summaries ( llm , summaries , user , & on_partial_blk )
prompt = DiscourseAi :: Completions :: Prompt . new ( << ~ TEXT . strip )
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 . push ( type : :user , content : << ~ TEXT . strip )
THESE are the summaries , each one separated by a newline , all of them inside < input > < / input> XML tags:
< input >
#{summaries.join("\n")}
< / input>
TEXT
llm . generate ( prompt , user : user , & on_partial_blk )
end
2023-11-23 12:58:54 -03:00
def summarization_prompt ( input , opts )
2024-03-20 16:33:05 +11:00
insts = + << ~ TEXT
You are an advanced summarization bot that generates concise , coherent summaries of provided text .
- Only include the summary , without any additional commentary .
- You understand and generate Discourse forum Markdown ; including links , _italics_ , ** bold ** .
- Maintain the original language of the text being summarized .
- Aim for summaries to be 400 words or less .
2023-11-23 16:33:37 -03:00
2023-11-23 12:58:54 -03:00
TEXT
2024-03-20 16:33:05 +11:00
insts << << ~ TEXT if opts [ :resource_path ]
- Each post is formatted as " <POST_NUMBER>) <USERNAME> <MESSAGE> "
- Cite specific noteworthy posts using the format [ NAME ] ( #{opts[:resource_path]}/POST_NUMBER)
- Example : link to the 3 rd post by sam : [ sam ] ( #{opts[:resource_path]}/3)
- Example : link to the 6 th post by jane : [ agreed with ] ( #{opts[:resource_path]}/6)
- Example : link to the 13 th post by joe : [ #13](#{opts[:resource_path]}/13)
- When formatting usernames either use @USERNMAE OR [ USERNAME ] ( #{opts[:resource_path]}/POST_NUMBER)
2023-11-23 12:58:54 -03:00
TEXT
2024-01-12 14:36:44 -03:00
prompt = DiscourseAi :: Completions :: Prompt . new ( insts . strip )
2023-11-23 12:58:54 -03:00
if opts [ :resource_path ]
2024-01-12 14:36:44 -03:00
prompt . push (
type : :user ,
content :
2024-03-20 16:33:05 +11:00
" Here are the posts inside <input></input> XML tags: \n \n <input>1) user1 said: I love Mondays 2) user2 said: I hate Mondays</input> \n \n Generate a concise, coherent summary of the text above maintaining the original language. " ,
2024-01-12 14:36:44 -03:00
)
prompt . push (
type : :model ,
content :
2024-03-20 16:33:05 +11:00
" Two users are sharing their feelings toward Mondays. [user1]( #{ opts [ :resource_path ] } /1) hates them, while [user2]( #{ opts [ :resource_path ] } /2) loves them. " ,
2024-01-12 14:36:44 -03:00
)
2023-11-23 12:58:54 -03:00
end
2024-01-12 14:36:44 -03:00
prompt . push ( type : :user , content : << ~ TEXT . strip )
#{opts[:content_title].present? ? "The discussion title is: " + opts[:content_title] + ".\n" : ""}
Here are the posts , inside < input > < / input> XML tags:
< input >
#{input}
< / input>
2024-03-20 16:33:05 +11:00
Generate a concise , coherent summary of the text above maintaining the original language .
2024-01-12 14:36:44 -03:00
TEXT
2023-11-23 12:58:54 -03:00
prompt
end
2023-06-27 12:26:33 -03:00
end
end
end
end