初始化:记忆系统源代码上传(已脱敏)
- 排除 .env / *.bak / 内部运维文档(README_INTERNAL.html) - config.py 默认密码已替换为占位符 CHANGE_ME_* - init_db.sql 移除生产数据库用户 GRANT 段 - README.html 数据库用户名已脱敏 - 保留:源码 + 公网 API 文档 + 建表 SQL(无授权语句)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""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. 每条事实:content(50-200字)、importance(0.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)}
|
||||
Reference in New Issue
Block a user