Merge pull request #866: refactor(memory): use tool call instead of JSON text for memory consolidation
This commit is contained in:
@@ -9,7 +9,6 @@ from contextlib import AsyncExitStack
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Awaitable, Callable
|
from typing import TYPE_CHECKING, Awaitable, Callable
|
||||||
|
|
||||||
import json_repair
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from nanobot.agent.context import ContextBuilder
|
from nanobot.agent.context import ContextBuilder
|
||||||
@@ -479,54 +478,58 @@ 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}"""
|
||||||
|
|
||||||
**IMPORTANT**: Both values MUST be strings, not objects or arrays.
|
save_memory_tool = [
|
||||||
|
{
|
||||||
Example:
|
"type": "function",
|
||||||
{{
|
"function": {
|
||||||
"history_entry": "[2026-02-14 22:50] User asked about...",
|
"name": "save_memory",
|
||||||
"memory_update": "- Host: HARRYBOOK-T14P\n- Name: Nado"
|
"description": "Save the memory consolidation result to persistent storage.",
|
||||||
}}
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
Respond with ONLY valid JSON, no markdown fences."""
|
"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("Memory consolidation: unexpected response type, skipping. Response: {}", text[:200])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if entry := result.get("history_entry"):
|
args = response.tool_calls[0].arguments
|
||||||
# Defensive: ensure entry is a string (LLM may return dict)
|
if entry := args.get("history_entry"):
|
||||||
if not isinstance(entry, str):
|
if not isinstance(entry, str):
|
||||||
entry = json.dumps(entry, ensure_ascii=False)
|
entry = json.dumps(entry, ensure_ascii=False)
|
||||||
memory.append_history(entry)
|
memory.append_history(entry)
|
||||||
if update := result.get("memory_update"):
|
if update := args.get("memory_update"):
|
||||||
# Defensive: ensure update is a string
|
|
||||||
if not isinstance(update, str):
|
if not isinstance(update, str):
|
||||||
update = json.dumps(update, ensure_ascii=False)
|
update = json.dumps(update, ensure_ascii=False)
|
||||||
if update != current_memory:
|
if update != current_memory:
|
||||||
|
|||||||
Reference in New Issue
Block a user