Use convertToLlm before serializing, include thinking, remove truncation

- serializeConversation now takes Message[] (after convertToLlm)
- Handles all custom message types via convertToLlm
- Includes thinking blocks as [Assistant thinking]
- Removes truncation of tool args and results (already token-budgeted)
This commit is contained in:
Mario Zechner 2025-12-30 00:01:43 +01:00
parent 2add465fbe
commit 17ce3814a8

View file

@ -6,7 +6,7 @@
*/ */
import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { AssistantMessage, Model, Usage, UserMessage } from "@mariozechner/pi-ai"; import type { AssistantMessage, Message, Model, Usage } from "@mariozechner/pi-ai";
import { complete, completeSimple } from "@mariozechner/pi-ai"; import { complete, completeSimple } from "@mariozechner/pi-ai";
import { convertToLlm, createBranchSummaryMessage, createHookMessage } from "../messages.js"; import { convertToLlm, createBranchSummaryMessage, createHookMessage } from "../messages.js";
import type { CompactionEntry, SessionEntry } from "../session-manager.js"; import type { CompactionEntry, SessionEntry } from "../session-manager.js";
@ -121,10 +121,11 @@ function formatFileOperations(readFiles: string[], modifiedFiles: string[]): str
} }
/** /**
* Serialize conversation messages to text for summarization. * Serialize LLM messages to text for summarization.
* This prevents the model from treating it as a conversation to continue. * This prevents the model from treating it as a conversation to continue.
* Call convertToLlm() first to handle custom message types.
*/ */
function serializeConversation(messages: AgentMessage[]): string { function serializeConversation(messages: Message[]): string {
const parts: string[] = []; const parts: string[] = [];
for (const msg of messages) { for (const msg of messages) {
@ -137,38 +138,41 @@ function serializeConversation(messages: AgentMessage[]): string {
.map((c) => c.text) .map((c) => c.text)
.join(""); .join("");
if (content) parts.push(`[User]: ${content}`); if (content) parts.push(`[User]: ${content}`);
} else if (msg.role === "assistant" && "content" in msg && Array.isArray(msg.content)) { } else if (msg.role === "assistant") {
const textParts: string[] = []; const textParts: string[] = [];
const thinkingParts: string[] = [];
const toolCalls: string[] = []; const toolCalls: string[] = [];
for (const block of msg.content) { for (const block of msg.content) {
if (block.type === "text") { if (block.type === "text") {
textParts.push(block.text); textParts.push(block.text);
} else if (block.type === "thinking") {
thinkingParts.push(block.thinking);
} else if (block.type === "toolCall") { } else if (block.type === "toolCall") {
const args = block.arguments as Record<string, unknown>; const args = block.arguments as Record<string, unknown>;
const argsStr = Object.entries(args) const argsStr = Object.entries(args)
.map(([k, v]) => `${k}=${JSON.stringify(v).slice(0, 100)}`) .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
.join(", "); .join(", ");
toolCalls.push(`${block.name}(${argsStr})`); toolCalls.push(`${block.name}(${argsStr})`);
} }
} }
if (thinkingParts.length > 0) {
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
}
if (textParts.length > 0) { if (textParts.length > 0) {
parts.push(`[Assistant]: ${textParts.join("\n")}`); parts.push(`[Assistant]: ${textParts.join("\n")}`);
} }
if (toolCalls.length > 0) { if (toolCalls.length > 0) {
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`); parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
} }
} else if (msg.role === "toolResult" && "content" in msg) { } else if (msg.role === "toolResult") {
// Summarize tool results briefly const content = msg.content
const content = Array.isArray(msg.content)
? msg.content
.filter((c): c is { type: "text"; text: string } => c.type === "text") .filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text.slice(0, 500)) .map((c) => c.text)
.join("") .join("");
: "";
if (content) { if (content) {
parts.push(`[Tool result]: ${content.slice(0, 1000)}${content.length > 1000 ? "..." : ""}`); parts.push(`[Tool result]: ${content}`);
} }
} }
} }
@ -595,7 +599,9 @@ export async function generateSummary(
} }
// Serialize conversation to text so model doesn't try to continue it // Serialize conversation to text so model doesn't try to continue it
const conversationText = serializeConversation(currentMessages); // Convert to LLM messages first (handles custom types like bashExecution, hookMessage, etc.)
const llmMessages = convertToLlm(currentMessages);
const conversationText = serializeConversation(llmMessages);
// Build the prompt with conversation wrapped in tags // Build the prompt with conversation wrapped in tags
let promptText = `<conversation>\n${conversationText}\n</conversation>\n\n`; let promptText = `<conversation>\n${conversationText}\n</conversation>\n\n`;