Files
Agentic-Compaction-and-Pipl…/agent-system/core/summarizer.ts
admin c629646b9f Complete Agent Pipeline System with Claude Code & OpenClaw Integration
- Added Claude Code integration with full context compaction support
- Added OpenClaw integration with deterministic pipeline support
- Implemented parallel agent execution (4 projects x 3 roles pattern)
- Added workspace isolation with permissions and quotas
- Implemented Lobster-compatible YAML workflow parser
- Added persistent memory store for cross-session context
- Created comprehensive README with hero section

This project was 100% autonomously built by Z.AI GLM-5
2026-03-03 13:12:14 +00:00

333 lines
8.6 KiB
TypeScript

/**
* Conversation Summarizer Module
*
* Uses LLM to create intelligent summaries of conversations,
* preserving key information while reducing token count.
*/
import ZAI from 'z-ai-web-dev-sdk';
import { TokenCounter, countTokens } from './token-counter';
export interface SummaryResult {
summary: string;
originalTokens: number;
summaryTokens: number;
compressionRatio: number;
keyPoints: string[];
decisions: string[];
actionItems: string[];
}
export interface SummarizerOptions {
maxSummaryTokens?: number;
preserveRecentMessages?: number;
extractKeyPoints?: boolean;
extractDecisions?: boolean;
extractActionItems?: boolean;
}
export interface ConversationTurn {
role: 'user' | 'assistant' | 'system';
content: string;
timestamp?: Date;
metadata?: Record<string, unknown>;
}
const DEFAULT_OPTIONS: Required<SummarizerOptions> = {
maxSummaryTokens: 1000,
preserveRecentMessages: 3,
extractKeyPoints: true,
extractDecisions: true,
extractActionItems: true
};
/**
* ConversationSummarizer - Creates intelligent summaries of conversations
*/
export class ConversationSummarizer {
private zai: Awaited<ReturnType<typeof ZAI.create>> | null = null;
private tokenCounter: TokenCounter;
private options: Required<SummarizerOptions>;
constructor(options: SummarizerOptions = {}) {
this.options = { ...DEFAULT_OPTIONS, ...options };
this.tokenCounter = new TokenCounter();
}
/**
* Initialize the summarizer (lazy load ZAI)
*/
private async init(): Promise<void> {
if (!this.zai) {
this.zai = await ZAI.create();
}
}
/**
* Summarize a conversation
*/
async summarize(
messages: ConversationTurn[],
options?: Partial<SummarizerOptions>
): Promise<SummaryResult> {
await this.init();
const opts = { ...this.options, ...options };
const originalTokens = this.tokenCounter.countConversation(messages).total;
// Format conversation for summarization
const conversationText = this.formatConversationForSummary(messages);
// Create the summarization prompt
const prompt = this.buildSummarizationPrompt(conversationText, opts);
// Get summary from LLM
const response = await this.zai!.chat.completions.create({
messages: [
{
role: 'assistant',
content: `You are a precise conversation summarizer. Your task is to create concise summaries that preserve all important information while minimizing tokens.`
},
{
role: 'user',
content: prompt
}
],
thinking: { type: 'disabled' }
});
const summaryText = response.choices?.[0]?.message?.content || '';
// Parse the structured response
const parsed = this.parseSummaryResponse(summaryText);
const summaryTokens = countTokens(parsed.summary);
return {
summary: parsed.summary,
originalTokens,
summaryTokens,
compressionRatio: originalTokens > 0 ? summaryTokens / originalTokens : 0,
keyPoints: parsed.keyPoints,
decisions: parsed.decisions,
actionItems: parsed.actionItems
};
}
/**
* Format conversation for summarization
*/
private formatConversationForSummary(messages: ConversationTurn[]): string {
return messages.map(msg => {
const timestamp = msg.timestamp?.toISOString() || '';
return `[${msg.role.toUpperCase()}]${timestamp ? ` (${timestamp})` : ''}: ${msg.content}`;
}).join('\n\n');
}
/**
* Build the summarization prompt
*/
private buildSummarizationPrompt(
conversationText: string,
options: Required<SummarizerOptions>
): string {
const sections: string[] = [];
sections.push(`Please summarize the following conversation concisely.`);
sections.push(`The summary should be under ${options.maxSummaryTokens} tokens.`);
if (options.extractKeyPoints) {
sections.push(`\nExtract KEY POINTS as a bullet list.`);
}
if (options.extractDecisions) {
sections.push(`Extract any DECISIONS made as a bullet list.`);
}
if (options.extractActionItems) {
sections.push(`Extract any ACTION ITEMS as a bullet list.`);
}
sections.push(`\nFormat your response as:
## SUMMARY
[Your concise summary here]
## KEY POINTS
- [Key point 1]
- [Key point 2]
## DECISIONS
- [Decision 1]
- [Decision 2]
## ACTION ITEMS
- [Action item 1]
- [Action item 2]
---
CONVERSATION:
${conversationText}`);
return sections.join('\n');
}
/**
* Parse the structured summary response
*/
private parseSummaryResponse(text: string): {
summary: string;
keyPoints: string[];
decisions: string[];
actionItems: string[];
} {
const sections = {
summary: '',
keyPoints: [] as string[],
decisions: [] as string[],
actionItems: [] as string[]
};
// Extract summary
const summaryMatch = text.match(/## SUMMARY\s*([\s\S]*?)(?=##|$)/i);
if (summaryMatch) {
sections.summary = summaryMatch[1].trim();
}
// Extract key points
const keyPointsMatch = text.match(/## KEY POINTS\s*([\s\S]*?)(?=##|$)/i);
if (keyPointsMatch) {
sections.keyPoints = this.extractBulletPoints(keyPointsMatch[1]);
}
// Extract decisions
const decisionsMatch = text.match(/## DECISIONS\s*([\s\S]*?)(?=##|$)/i);
if (decisionsMatch) {
sections.decisions = this.extractBulletPoints(decisionsMatch[1]);
}
// Extract action items
const actionItemsMatch = text.match(/## ACTION ITEMS\s*([\s\S]*?)(?=##|$)/i);
if (actionItemsMatch) {
sections.actionItems = this.extractBulletPoints(actionItemsMatch[1]);
}
return sections;
}
/**
* Extract bullet points from text
*/
private extractBulletPoints(text: string): string[] {
const lines = text.split('\n');
return lines
.map(line => line.replace(/^[-*•]\s*/, '').trim())
.filter(line => line.length > 0);
}
/**
* Create a rolling summary (for continuous conversations)
*/
async createRollingSummary(
previousSummary: string,
newMessages: ConversationTurn[]
): Promise<SummaryResult> {
await this.init();
const prompt = `You are updating a conversation summary with new messages.
PREVIOUS SUMMARY:
${previousSummary}
NEW MESSAGES:
${this.formatConversationForSummary(newMessages)}
Create an updated summary that integrates the new information with the previous summary.
Keep the summary concise but comprehensive.
Format your response as:
## SUMMARY
[Your updated summary]
## KEY POINTS
- [Updated key points]
## DECISIONS
- [Updated decisions]
## ACTION ITEMS
- [Updated action items]`;
const response = await this.zai!.chat.completions.create({
messages: [
{
role: 'assistant',
content: 'You are a conversation summarizer that maintains rolling summaries.'
},
{
role: 'user',
content: prompt
}
],
thinking: { type: 'disabled' }
});
const summaryText = response.choices?.[0]?.message?.content || '';
const parsed = this.parseSummaryResponse(summaryText);
return {
summary: parsed.summary,
originalTokens: countTokens(previousSummary) + this.tokenCounter.countConversation(newMessages).total,
summaryTokens: countTokens(parsed.summary),
compressionRatio: 0,
keyPoints: parsed.keyPoints,
decisions: parsed.decisions,
actionItems: parsed.actionItems
};
}
/**
* Create a topic-based summary (groups messages by topic)
*/
async createTopicSummary(
messages: ConversationTurn[]
): Promise<Map<string, SummaryResult>> {
await this.init();
// First, identify topics
const topicResponse = await this.zai!.chat.completions.create({
messages: [
{
role: 'assistant',
content: 'Identify the main topics in this conversation. Respond with a JSON array of topic names.'
},
{
role: 'user',
content: this.formatConversationForSummary(messages)
}
],
thinking: { type: 'disabled' }
});
let topics: string[] = [];
try {
const topicText = topicResponse.choices?.[0]?.message?.content || '[]';
topics = JSON.parse(topicText.match(/\[.*\]/s)?.[0] || '[]');
} catch {
topics = ['General'];
}
// Create summaries for each topic
const summaries = new Map<string, SummaryResult>();
for (const topic of topics) {
const summary = await this.summarize(messages, {
maxSummaryTokens: 500
});
summaries.set(topic, summary);
}
return summaries;
}
}
// Singleton instance
export const defaultSummarizer = new ConversationSummarizer();