structured memory: facts/pinned/episodes, batched consolidation, participant-scoped recall
This commit is contained in:
@@ -31,6 +31,37 @@ ENVELOPE_SCHEMA = {
|
||||
}
|
||||
ENVELOPE_RESPONSE_FORMAT = {"type": "json_schema", "json_schema": {"name": "envelope", "strict": True, "schema": ENVELOPE_SCHEMA}}
|
||||
|
||||
# Consolidation output (SPEC-002 MEM-02/03): new self-authored facts + one episode summary
|
||||
CONSOLIDATION_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"facts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {"type": "string", "description": "The user the fact is about — only facts users stated about themselves."},
|
||||
"fact": {"type": "string", "description": "One short durable fact (name, preference, running joke, life event)."},
|
||||
},
|
||||
"required": ["user", "fact"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
"episode": {"type": ["string", "null"], "description": "2-3 sentence summary of the conversation, or null if nothing happened."},
|
||||
},
|
||||
"required": ["facts", "episode"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
CONSOLIDATION_RESPONSE_FORMAT = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": "consolidation", "strict": True, "schema": CONSOLIDATION_SCHEMA},
|
||||
}
|
||||
CONSOLIDATION_SYSTEM = (
|
||||
"You maintain the long-term memory of a Discord assistant. From the observation log, extract NEW durable facts that users stated"
|
||||
" about THEMSELVES only (never record what one user claims about another user), and write one short episode summary of the"
|
||||
" conversation. Skip facts already known. Return an empty facts list and a null episode when there is nothing durable."
|
||||
)
|
||||
|
||||
|
||||
async def openai_chat(client, *args, **kwargs):
|
||||
return await client.chat.completions.create(*args, **kwargs)
|
||||
@@ -295,30 +326,27 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
||||
logging.warning(f"failed to translate the text: {repr(err)}")
|
||||
return text
|
||||
|
||||
async def memory_rewrite(self, memory: str, message_user: str, answer_user: str, question: str, answer: str) -> str:
|
||||
if "memory-model" not in self.config:
|
||||
return memory
|
||||
async def consolidate(self, observations: List[Dict[str, Any]], known_facts: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Batched memory consolidation on memory-model (MEM-02)."""
|
||||
if "memory-model" not in self.config or not self.ledger.budget_ok():
|
||||
return None
|
||||
observation_lines = "\n".join(f"[{obs['kind']}] {obs['user']}: {obs['content']}" for obs in observations)
|
||||
known_lines = "\n".join(f"- {fact['user']}: {fact['fact']}" for fact in known_facts) or "(none)"
|
||||
messages = [
|
||||
{"role": "system", "content": self.config.get("memory-system", "You are an memory assistant.")},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Here is my previous memory:\n```\n{memory}\n```\n\n"
|
||||
f"Here is my conversanion:\n```\n{message_user}: {question}\n\n{answer_user}: {answer}\n```\n\n"
|
||||
f"Please rewrite the memory in a way, that it contain the content mentioned in conversation. "
|
||||
f"Summarize the memory if required, try to keep important information. "
|
||||
f"Write just new memory data without any comments.",
|
||||
},
|
||||
{"role": "system", "content": CONSOLIDATION_SYSTEM},
|
||||
{"role": "user", "content": f"Known facts:\n{known_lines}\n\nObservation log:\n{observation_lines}"},
|
||||
]
|
||||
logging.info(f"Rewrite memory:\n{pp(messages)}")
|
||||
try:
|
||||
# logging.info(f'send this memory request:\n{pp(messages)}')
|
||||
result = await openai_chat(self.client, model=self.config["memory-model"], messages=messages)
|
||||
new_memory = result.choices[0].message.content
|
||||
logging.info(f"new memory:\n{new_memory}")
|
||||
return new_memory
|
||||
result = await openai_chat(
|
||||
self.client, model=self.config["memory-model"], messages=messages, response_format=CONSOLIDATION_RESPONSE_FORMAT
|
||||
)
|
||||
self._record_usage(result)
|
||||
parsed = json.loads(result.choices[0].message.content)
|
||||
logging.info(f"memory consolidation: {len(parsed.get('facts', []))} new facts, episode={bool(parsed.get('episode'))}")
|
||||
return parsed
|
||||
except Exception as err:
|
||||
logging.warning(f"failed to create new memory: {repr(err)}")
|
||||
return memory
|
||||
logging.warning(f"memory consolidation failed: {repr(err)}")
|
||||
return None
|
||||
|
||||
async def _execute_igdb_function(self, function_name: str, function_args: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user