Files
memory-system/lifecycle/fact_extractor.py
T
zqf b1f93588f1 初始化:记忆系统源代码上传(已脱敏)
- 排除 .env / *.bak / 内部运维文档(README_INTERNAL.html)
- config.py 默认密码已替换为占位符 CHANGE_ME_*
- init_db.sql 移除生产数据库用户 GRANT 段
- README.html 数据库用户名已脱敏
- 保留:源码 + 公网 API 文档 + 建表 SQL(无授权语句)
2026-08-03 04:53:48 +08:00

102 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Fact Extractor - Extract structured atomic facts from working memory using LLM."""
import json
import logging
from typing import Optional, List
from lifecycle.llm_parse import parse_llm_json_array
logger = logging.getLogger(__name__)
FACT_SYSTEM_PROMPT = """你是JSON生成器。输入工作记忆,直接提取结构化事实。
严格规则:
1. 禁止输出分析、推理、思考、解释
2. 第一个字符必须是 [,最后一个字符必须是 ]
3. 每条事实:content50-200字)、importance0.0-1.0)、category(分类)
4. 最多20条,无值得提取的事实返回 []
输出格式(只输出JSON,不输出其他任何文字):
[{"content":"事实","importance":0.8,"category":"分类"}]"""
class FactExtractor:
"""Extract atomic facts from working memory using LLM."""
def __init__(self, mysql_store, redis_cache, llm_client, embedding_service):
self.mysql = mysql_store
self.redis = redis_cache
self.llm = llm_client
self.embedder = embedding_service
def extract(self, agent_id: str, team_id: str,
max_memories: int = 20,
delete_after: bool = False) -> dict:
"""Extract structured facts from working memory."""
if not self.llm.available:
return {"error": "LLM not configured. Set LLM_API_URL, LLM_API_KEY, LLM_MODEL environment variables."}
items = self.redis.get_working_memories(agent_id, limit=max_memories)
if not items:
return {"facts": [], "count": 0}
memory_lines = []
for i, item in enumerate(items):
if not isinstance(item, dict):
logger.warning('Skipping non-dict working memory item: %r', item)
continue
content = item.get("content", "")[:300]
memory_lines.append(f"[{i+1}] {content}")
user_prompt = "以下是需要提取事实的工作记忆:\n\n" + "\n".join(memory_lines)
try:
facts = parse_llm_json_array(
self.llm, FACT_SYSTEM_PROMPT, user_prompt,
temperature=0.2, context="extract-facts",
)
except ValueError as e:
return {"error": str(e)}
stored = []
for fact in facts:
if not isinstance(fact, dict):
logger.warning('Skipping non-dict fact from LLM: %r', fact)
continue
content = fact.get("content", "").strip()
if not content or len(content) < 10:
continue
importance = fact.get("importance", 0.6)
if not isinstance(importance, (int, float)) or importance < 0 or importance > 1:
importance = 0.6
category = fact.get("category", "")
embedding = self.embedder.embed(content)
from storage.vector_search import embedding_to_bytes
emb_bytes = embedding_to_bytes(embedding)
memory_id = self.mysql.add_personal_memory(
agent_id=agent_id,
team_id=team_id,
content=content,
embedding=emb_bytes,
importance=float(importance),
metadata={
"source": "fact_extraction",
"category": category,
"extracted_from": "working_memory",
},
)
stored.append({
"id": memory_id,
"content": content,
"importance": importance,
"category": category,
})
if delete_after and stored:
self.redis.clear_working_memory(agent_id)
logger.info(f"Deleted working memories for {agent_id} after extracting {len(stored)} facts")
return {"facts": stored, "count": len(stored)}