fix: 修复 NameError/状态污染/类型标注/统计; 补全练习与 main; 新增 config/.gitignore/requirements; 文档统一

This commit is contained in:
agent
2026-06-02 13:44:46 +08:00
parent ef876a22d1
commit 908431e25f
23 changed files with 919 additions and 77 deletions
-13
View File
@@ -68,19 +68,6 @@ class Message:
}
@dataclass
class MemorySnapshot:
"""记忆快照 - 用于保存和恢复状态"""
state: Dict[str, Any] # 关键状态
messages: List[Message] # 消息历史
key_info: Dict[str, Any] # 关键信息摘要
timestamp: str = ""
def __post_init__(self):
if not self.timestamp:
self.timestamp = datetime.now().isoformat()
# ═══════════════════════════════════════════════════════════════════════════════
# 第三部分:Working Memory(工作记忆)
# ═══════════════════════════════════════════════════════════════════════════════
+82
View File
@@ -0,0 +1,82 @@
"""
Step 04 练习题:Memory 实战
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 练习目标:
1. 理解三层记忆的协作方式
2. 实现一个 Token 估算器
3. 体验摘要压缩的副作用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 1Token 估算
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
在 ShortTermMemory 上加一个 estimate_tokens() 方法,粗略估计当前占用的 token 数。
要求:
1. 简单规则:1 个中文字符 ≈ 1.5 token1 个英文单词 ≈ 1.3 token
2. 对所有消息求和
3. 返回 int(向上取整)
提示:
- 正则分中英文:re.findall(r'[\u4e00-\u9fff]', text) 取汉字,剩下按空格分词
- import math; math.ceil(...)
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 2:基于 Token 阈值的自动压缩
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
给 ShortTermMemory 加一个 maybe_compress(max_tokens: int) 方法:
当 estimate_tokens() 超过 max_tokens 时,把较早的对话压缩成一行摘要,
保留最近的 5 条。
要求:
1. 触发时调用 summarize_older(keep_recent=5)
2. 把摘要作为一个新的 Message(role="system", content=summary) 放回 messages 头部
3. 删除被摘要覆盖的旧消息(避免 token 没降反升)
"""
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 3:把 MemorySystem 接到 SimpleAgent
# ═══════════════════════════════════════════════════════════════════════════════
"""
任务:
让 SimpleAgent 在 process() 时把每一轮对话写入 MemorySystem
并在下次决策前把 memory.get_context() 注入到 state['context'] 中。
要求:
1. SimpleAgent.__init__ 里 new 一个 MemorySystem
2. process() 末尾:self.memory.add_message('user' / 'assistant', ...)
3. process() 开头:self.state['context'] = self.memory.get_context()
提示:
- 直接修改 step_03/concept.py 是允许的(学习项目不是发布包)
- 可以通过 Monkey-patching 避免破坏 step_03 原有行为
"""
def test_exercises():
from step_04_memory.concept import MemorySystem
mem = MemorySystem()
mem.add_message("user", "帮我生成销售月报")
mem.add_message("assistant", "好的,请告诉我字段")
print("上下文片段:")
print(mem.get_context()[:200])
if __name__ == "__main__":
test_exercises()
+118
View File
@@ -0,0 +1,118 @@
"""
Step 04 练习题答案
⚠️ 先自己思考,再看答案!
⚠️ 答案不是唯一的,这里只是其中一种实现
"""
import math
import re
from step_04_memory.concept import MemorySystem, Message, ShortTermMemory
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 1 答案:Token 估算
# ═══════════════════════════════════════════════════════════════════════════════
_CN_PATTERN = re.compile(r"[\u4e00-\u9fff]")
_EN_WORD = re.compile(r"[A-Za-z]+")
def estimate_tokens_for_text(text: str) -> int:
cn = len(_CN_PATTERN.findall(text))
en = len(_EN_WORD.findall(text))
return math.ceil(cn * 1.5 + en * 1.3)
def install_estimate_tokens() -> None:
def estimate_tokens(self: ShortTermMemory) -> int:
return sum(estimate_tokens_for_text(m.content) for m in self.messages)
ShortTermMemory.estimate_tokens = estimate_tokens
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 2 答案:基于 Token 阈值的自动压缩
# ═══════════════════════════════════════════════════════════════════════════════
def install_maybe_compress() -> None:
def maybe_compress(self: ShortTermMemory, max_tokens: int = 800) -> bool:
if not hasattr(self, "estimate_tokens"):
raise RuntimeError("请先调用 install_estimate_tokens()")
if self.estimate_tokens() <= max_tokens:
return False
summary = self.summarize_older(keep_recent=5)
if not summary:
return False
# 保留最近 5 条,把摘要作为 system message 放最前
recent = self.messages[-5:]
self.messages = [Message(role="system", content=f"[历史摘要]\n{summary}")] + recent
return True
ShortTermMemory.maybe_compress = maybe_compress
# ═══════════════════════════════════════════════════════════════════════════════
# 练习 3 答案:把 MemorySystem 接到 SimpleAgent
# ═══════════════════════════════════════════════════════════════════════════════
def install_memory_to_agent() -> None:
from step_03_simple_agent.concept import SimpleAgent
orig_init = SimpleAgent.__init__
def patched_init(self, *args, **kwargs):
orig_init(self, *args, **kwargs)
self.memory = MemorySystem()
SimpleAgent.__init__ = patched_init
orig_process = SimpleAgent.process
def patched_process(self, user_input: str) -> str:
# 把记忆上下文注入 state
self.state["context"] = self.memory.get_context()
response = orig_process(self, user_input)
# 记录本轮对话
self.memory.add_message("user", user_input)
self.memory.add_message("assistant", response)
return response
SimpleAgent.process = patched_process
# ═══════════════════════════════════════════════════════════════════════════════
# 测试
# ═══════════════════════════════════════════════════════════════════════════════
def test_answers():
print("\n" + "=" * 60)
print("Step 04 练习答案测试")
print("=" * 60)
install_estimate_tokens()
install_maybe_compress()
mem = MemorySystem()
for i in range(20):
mem.short_term.add("user", f"{i} 轮对话内容,包含中文与 english words " * 5)
print(f"\n📝 练习 1: 注入 20 条后估算 token = {mem.short_term.estimate_tokens()}")
compressed = mem.short_term.maybe_compress(max_tokens=200)
print(f" maybe_compress() = {compressed}, 压缩后消息数 = {len(mem.short_term.messages)}")
print(f" 压缩后估算 token = {mem.short_term.estimate_tokens()}")
print("\n📝 练习 3: SimpleAgent 接入 Memory")
try:
install_memory_to_agent()
from step_03_simple_agent.concept import SimpleAgent
agent = SimpleAgent()
agent.process("1 + 2")
print(f" agent.memory 工作正常,消息数 = {len(agent.memory.short_term.messages)}")
except Exception as e:
print(f" 接入失败(可忽略,需在 step_03 父目录运行): {e}")
if __name__ == "__main__":
test_answers()
+17
View File
@@ -0,0 +1,17 @@
"""
Step 04: Memory - 记忆系统 主程序
运行方式:
cd step_04_memory
python main.py
"""
from concept import demo
def main():
demo()
if __name__ == "__main__":
main()