fix(memory): Enforce memory consolidation schema with a tool call
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
import json
|
import json
|
||||||
import json_repair
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from typing import Any, Awaitable, Callable
|
from typing import Any, Awaitable, Callable
|
||||||
@@ -439,42 +438,56 @@ class AgentLoop:
|
|||||||
conversation = "\n".join(lines)
|
conversation = "\n".join(lines)
|
||||||
current_memory = memory.read_long_term()
|
current_memory = memory.read_long_term()
|
||||||
|
|
||||||
prompt = f"""You are a memory consolidation agent. Process this conversation and return a JSON object with exactly two keys:
|
prompt = f"""Process this conversation and call the save_memory tool with your consolidation.
|
||||||
|
|
||||||
1. "history_entry": A paragraph (2-5 sentences) summarizing the key events/decisions/topics. Start with a timestamp like [YYYY-MM-DD HH:MM]. Include enough detail to be useful when found by grep search later.
|
|
||||||
|
|
||||||
2. "memory_update": The updated long-term memory content. Add any new facts: user location, preferences, personal info, habits, project context, technical decisions, tools/services used. If nothing new, return the existing content unchanged.
|
|
||||||
|
|
||||||
## Current Long-term Memory
|
## Current Long-term Memory
|
||||||
{current_memory or "(empty)"}
|
{current_memory or "(empty)"}
|
||||||
|
|
||||||
## Conversation to Process
|
## Conversation to Process
|
||||||
{conversation}
|
{conversation}"""
|
||||||
|
|
||||||
Respond with ONLY valid JSON, no markdown fences."""
|
save_memory_tool = [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "save_memory",
|
||||||
|
"description": "Save the memory consolidation result to persistent storage.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"history_entry": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A paragraph (2-5 sentences) summarizing key events/decisions/topics. Start with a timestamp like [YYYY-MM-DD HH:MM]. Include enough detail to be useful when found by grep search later.",
|
||||||
|
},
|
||||||
|
"memory_update": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The full updated long-term memory content as a markdown string. Include all existing facts plus any new facts: user location, preferences, personal info, habits, project context, technical decisions, tools/services used. If nothing new, return the existing content unchanged.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["history_entry", "memory_update"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self.provider.chat(
|
response = await self.provider.chat(
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": "You are a memory consolidation agent. Respond only with valid JSON."},
|
{"role": "system", "content": "You are a memory consolidation agent. Call the save_memory tool with your consolidation of the conversation."},
|
||||||
{"role": "user", "content": prompt},
|
{"role": "user", "content": prompt},
|
||||||
],
|
],
|
||||||
|
tools=save_memory_tool,
|
||||||
model=self.model,
|
model=self.model,
|
||||||
)
|
)
|
||||||
text = (response.content or "").strip()
|
|
||||||
if not text:
|
if not response.has_tool_calls:
|
||||||
logger.warning("Memory consolidation: LLM returned empty response, skipping")
|
logger.warning("Memory consolidation: LLM did not call save_memory tool, skipping")
|
||||||
return
|
|
||||||
if text.startswith("```"):
|
|
||||||
text = text.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
|
|
||||||
result = json_repair.loads(text)
|
|
||||||
if not isinstance(result, dict):
|
|
||||||
logger.warning(f"Memory consolidation: unexpected response type, skipping. Response: {text[:200]}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if entry := result.get("history_entry"):
|
args = response.tool_calls[0].arguments
|
||||||
|
if entry := args.get("history_entry"):
|
||||||
memory.append_history(entry)
|
memory.append_history(entry)
|
||||||
if update := result.get("memory_update"):
|
if update := args.get("memory_update"):
|
||||||
if update != current_memory:
|
if update != current_memory:
|
||||||
memory.write_long_term(update)
|
memory.write_long_term(update)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user