简打卡
51.76M · 2026-03-28
多数 Agent 项目并不是“不会回答”,而是“记不住”。
PostgresSaver + thread_id 保持会话连续。MEMORY.MD 持久化用户偏好和项目事实。这套方案的特点是结构清晰、可读可维护、适合开源项目从 0 到 1 的记忆能力建设。
在实际 Agent 里,记忆问题通常分两类:
对应地,我们把记忆拆成两层:
这不是概念区分,而是工程边界区分。前者强调时效和连贯,后者强调沉淀和复用。
flowchart LR
A["用户输入"] --> B["retrieve_memories 节点"]
B --> C["MEMORY.MD 检索"]
C --> D["long_term_context 注入 SystemMessage"]
D --> E["agent 节点调用 LLM"]
E -->|tool_calls| F["ToolNode"]
F -->|save_memory| G["写入 MEMORY.MD"]
F -->|execute_python| H["执行 Python"]
F --> E
E --> I["返回响应"]
J["PostgresSaver(checkpointer)"] <---> E
J <---> B
系统中的两条记忆链路:
PostgresSaver 管短期会话。MEMORY.MD 管长期知识。thread_id 就是会话上下文键核心思路:同一个 thread_id 下,LangGraph 的消息和状态自动连续。
# src/agents/cli.py
with PostgresSaver.from_conn_string(settings.postgres_dsn) as checkpointer:
if hasattr(checkpointer, "setup"):
checkpointer.setup()
graph = CyberCoreGraph(
llm=llm,
tools=tools,
memory_store=memory_store,
retrieve_top_k=settings.retrieve_top_k,
user_id=settings.user_id,
checkpointer=checkpointer,
).app
# src/agents/cli.py
config = {
"configurable": {
"thread_id": current_session_id,
}
}
inputs = {"messages": [HumanMessage(content=user_text)]}
for event in graph.stream(inputs, config=config, stream_mode="values"):
...
# src/agents/cli.py
session_store = SessionStateStore(settings.session_state_file_path)
current_session_id = session_store.load() or settings.session_id
session_store.save(current_session_id)
if user_text.lower() == "/new":
current_session_id = datetime.now().strftime("cli_session_%Y%m%d_%H%M%S")
session_store.save(current_session_id)
工程收益:
/new 一键开启新上下文,避免线程污染。MEMORY.MD 的四段式存储长期记忆文件结构固定为四个区块:
这让记忆“可读可审计”,非常适合开源协作。
# src/agents/memory.py
SECTION_ORDER = [
"User Information",
"Preferences",
"Project Context",
"Important Notes",
]
SECTION_PLACEHOLDERS = {
"User Information": "(Important facts about the user)",
"Preferences": "(User preferences learned over time)",
"Project Context": "(Information about ongoing projects)",
"Important Notes": "(Things to remember)",
}
# src/agents/memory.py
def _render_template(sections: dict[str, list[str]]) -> str:
lines: list[str] = [HEADER, "", DESCRIPTION, ""]
for section in SECTION_ORDER:
lines.append(f"## {section}")
lines.append("")
items = sections.get(section, [])
if items:
lines.extend(f"- {item}" for item in items)
else:
lines.append(SECTION_PLACEHOLDERS[section])
lines.append("")
lines.extend(["---", "", FOOTER, ""])
return "n".join(lines)
当模型判断“这条信息值得长期保留”时,调用 save_memory。
# src/agents/tools/runtime.py
@tool
def save_memory(text: str, memory_type: str = "experience") -> str:
doc_id = memory_store.save(user_id=user_id, text=text, memory_type=memory_type)
if not doc_id:
return "memory save skipped"
return f"memory saved, id={doc_id}"
# src/agents/memory.py
def save(self, user_id: str, text: str, memory_type: str = "experience") -> str:
clean_text = " ".join((text or "").strip().split())
if not clean_text:
return ""
section = _map_section(memory_type)
timestamp = datetime.now(tz=timezone.utc).isoformat(timespec="seconds")
entry = f"{timestamp} [{memory_type}] {clean_text}"
with self._lock:
sections = self._read_sections()
existing_normalized = {_normalize(item) for item in sections[section]}
if _normalize(clean_text) in existing_normalized:
return _stable_id(clean_text)
sections[section].append(entry)
self._compress_sections(sections, max_items_per_section=80)
self._write_sections(sections)
return _stable_id(entry)
# src/agents/memory.py
def _map_section(memory_type: str) -> str:
key = (memory_type or "").strip().lower()
if key in {"preference", "preferences"}:
return "Preferences"
if key in {"user", "profile", "user_info"}:
return "User Information"
if key in {"project", "context", "task"}:
return "Project Context"
return "Important Notes"
每轮对话开始先走 retrieve_memories 节点。
# src/agents/agent.py
def _retrieve_memories(self, state: AgentState, _: RunnableConfig | None = None) -> dict[str, str]:
query = _extract_last_user_message(state.get("messages", []))
if not query:
return {"long_term_context": ""}
docs = self._memory_store.retrieve(
user_id=self._user_id,
query=query,
k=self._retrieve_top_k,
)
if not docs:
docs = self._memory_store.retrieve_recent(
user_id=self._user_id,
k=self._retrieve_top_k,
)
if not docs:
return {"long_term_context": ""}
lines = [f"- {doc.page_content}" for doc in docs]
return {"long_term_context": "n".join(lines)}
# src/agents/memory.py
def _score_text(text: str, query: str) -> int:
query = (query or "").strip()
if not query:
return 0
if query in text:
return max(3, len(query))
text_l = text.lower()
tokens = _tokenize(query)
score = 0
for token in tokens:
if token and token in text_l:
score += len(token)
return score
# src/agents/agent.py
memory_context = state.get("long_term_context") or "无"
memory_block = f"已检索到的长期记忆:n{memory_context}"
messages = [
SystemMessage(content=system_prompt),
SystemMessage(content=memory_block),
*sanitized_history,
]
response = self._llm.invoke(messages)
MEMORY.MD 可读可审计,便于人工纠错。threading.Lock 只保证进程内并发安全。推荐演进路径:
BM25 + Embedding 混合召回。如果你正在做自己的 Agent,这套方案非常适合作为第一版记忆系统:
thread_id 稳住会话连续性。MEMORY.MD 沉淀用户和项目知识。先把记忆做“对”,再把记忆做“强”,这是更稳的工程路线。
Cursor 和 Claude Code:AI 编程的两种哲学
Day10 学习日志:用 LangChain 搭一套可落地的**制度 RAG**(检索 + 生成)
2026-03-28
2026-03-28