219232de74
以讲义内容为骨架迁移到标准目录格式: - officefile/ 主内容(12章 + 附录 + CC4SI补充) - dofile/ 代码示例(11个Python脚本) - data/ 图片资源 - output/ 生成输出(忽略) - Archive/ 归档旧目录(忽略) - .claude/skills/ 保留markdown-to-docx工具链 - .pandoc/ 保留CSL和本地化配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1189 lines
39 KiB
Markdown
1189 lines
39 KiB
Markdown
# 03.4 记忆与上下文
|
||
|
||
## 核心问题
|
||
|
||
> Agent如何记住过去的经验以改进未来表现?
|
||
> 如何区分短期记忆和长期记忆?
|
||
- 如何高效检索相关知识?
|
||
|
||
---
|
||
|
||
## 概念讲解
|
||
|
||
### 记忆的层次结构
|
||
|
||
```
|
||
Agent记忆系统的层次结构
|
||
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 感知输入 (Perception) │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 短期记忆 (Short-term Memory) │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ - 对话历史 │
|
||
│ - 当前任务状态 │
|
||
│ - 临时变量 │
|
||
│ - 容量有限 (~10^4 tokens) │
|
||
│ - 快速访问 │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│
|
||
↓ (选择性保存)
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 长期记忆 (Long-term Memory) │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 语义记忆 (Semantic Memory) │ │
|
||
│ │ - 领域知识 │ │
|
||
│ │ - 概念定义 │ │
|
||
│ │ - 规则和模式 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 情景记忆 (Episodic Memory) │ │
|
||
│ │ - 过往经验 │ │
|
||
│ │ - 任务历史 │ │
|
||
│ │ - 成功/失败案例 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 程序记忆 (Procedural Memory) │ │
|
||
│ │ - 技能和操作序列 │ │
|
||
│ │ - 工作流模板 │ │
|
||
│ │ - 最佳实践 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 代理记忆 (Agent Memory) │ │
|
||
│ │ - 工具调用记录 │ │
|
||
│ │ - 环境状态快照 │ │
|
||
│ │ - 用户偏好 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│
|
||
↓ (检索)
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 知识检索 (Retrieval) │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ - 语义搜索 │
|
||
│ - 关联推理 │
|
||
│ - 上下文匹配 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 记忆与上下文的区别
|
||
|
||
| 维度 | 记忆 (Memory) | 上下文 (Context) |
|
||
|-----|--------------|-----------------|
|
||
| 持久性 | 持久存储 | 临时激活 |
|
||
| 范围 | 全局积累 | 当前相关 |
|
||
| 检索 | 需要查询 | 直接可用 |
|
||
| 更新 | 增量写入 | 动态构建 |
|
||
| 成本 | 存储成本高 | 计算成本高 |
|
||
|
||
---
|
||
|
||
## 设计原理
|
||
|
||
### 短期记忆管理
|
||
|
||
```python
|
||
from typing import List, Dict, Any, Optional
|
||
from collections import deque
|
||
from dataclasses import dataclass, field
|
||
import time
|
||
|
||
|
||
@dataclass
|
||
class MemoryItem:
|
||
"""记忆项"""
|
||
content: Any
|
||
timestamp: float = field(default_factory=time.time)
|
||
importance: float = 1.0 # 0-1,重要性评分
|
||
access_count: int = 0
|
||
tags: List[str] = field(default_factory=list)
|
||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||
|
||
|
||
class ShortTermMemory:
|
||
"""
|
||
短期记忆:快速访问的临时存储
|
||
|
||
特点:
|
||
- 容量有限
|
||
- 快速读写
|
||
- 按时间顺序组织
|
||
- 基于重要性的淘汰策略
|
||
"""
|
||
|
||
def __init__(self, max_items: int = 1000, max_tokens: int = 10000):
|
||
self.max_items = max_items
|
||
self.max_tokens = max_tokens
|
||
self.items: deque[MemoryItem] = deque()
|
||
self.current_tokens = 0
|
||
|
||
def add(self, content: Any, importance: float = 1.0,
|
||
tags: List[str] = None, metadata: Dict = None) -> None:
|
||
"""添加记忆项"""
|
||
item = MemoryItem(
|
||
content=content,
|
||
importance=importance,
|
||
tags=tags or [],
|
||
metadata=metadata or {}
|
||
)
|
||
|
||
# 估算token数量(简化)
|
||
tokens = self._estimate_tokens(content)
|
||
self._ensure_capacity(tokens)
|
||
|
||
self.items.append(item)
|
||
self.current_tokens += tokens
|
||
|
||
def get_recent(self, n: int = 10) -> List[MemoryItem]:
|
||
"""获取最近的项目"""
|
||
return list(self.items)[-n:]
|
||
|
||
def get_by_tags(self, tags: List[str],
|
||
match_all: bool = False) -> List[MemoryItem]:
|
||
"""按标签检索"""
|
||
results = []
|
||
for item in self.items:
|
||
if match_all:
|
||
if all(tag in item.tags for tag in tags):
|
||
results.append(item)
|
||
else:
|
||
if any(tag in item.tags for tag in tags):
|
||
results.append(item)
|
||
return results
|
||
|
||
def get_context_window(self, max_tokens: int = 4000) -> str:
|
||
"""
|
||
获取上下文窗口(用于LLM输入)
|
||
|
||
策略:优先保留重要且最近的内容
|
||
"""
|
||
# 按重要性和时间排序
|
||
scored_items = [
|
||
(item, self._score_item(item))
|
||
for item in self.items
|
||
]
|
||
scored_items.sort(key=lambda x: x[1], reverse=True)
|
||
|
||
# 构建上下文
|
||
context_parts = []
|
||
used_tokens = 0
|
||
|
||
for item, _ in scored_items:
|
||
content = str(item.content)
|
||
tokens = self._estimate_tokens(content)
|
||
|
||
if used_tokens + tokens > max_tokens:
|
||
break
|
||
|
||
context_parts.append(content)
|
||
used_tokens += tokens
|
||
item.access_count += 1
|
||
|
||
return "\n\n".join(context_parts)
|
||
|
||
def _score_item(self, item: MemoryItem) -> float:
|
||
"""
|
||
计算记忆项的得分
|
||
|
||
得分 = 重要性 * (1 + 访问次数) * 时间衰减
|
||
"""
|
||
age = time.time() - item.timestamp
|
||
time_decay = 2 ** (-age / 3600) # 每小时衰减一半
|
||
access_boost = 1 + item.access_count * 0.1
|
||
return item.importance * access_boost * time_decay
|
||
|
||
def _ensure_capacity(self, new_tokens: int) -> None:
|
||
"""确保有足够容量"""
|
||
while (len(self.items) >= self.max_items or
|
||
self.current_tokens + new_tokens > self.max_tokens):
|
||
if not self.items:
|
||
break
|
||
|
||
# 移除得分最低的项
|
||
min_score_item = min(self.items, key=self._score_item)
|
||
self.current_tokens -= self._estimate_tokens(min_score_item.content)
|
||
self.items.remove(min_score_item)
|
||
|
||
def _estimate_tokens(self, content: Any) -> int:
|
||
"""估算token数量(简化版)"""
|
||
return len(str(content)) // 4
|
||
|
||
def clear(self) -> None:
|
||
"""清空短期记忆"""
|
||
self.items.clear()
|
||
self.current_tokens = 0
|
||
```
|
||
|
||
### 长期记忆存储
|
||
|
||
```python
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Union, List
|
||
from abc import ABC, abstractmethod
|
||
import hashlib
|
||
|
||
|
||
class MemoryStore(ABC):
|
||
"""记忆存储抽象基类"""
|
||
|
||
@abstractmethod
|
||
def store(self, key: str, value: Any, metadata: Dict = None) -> bool:
|
||
"""存储"""
|
||
pass
|
||
|
||
@abstractmethod
|
||
def retrieve(self, key: str) -> Optional[Any]:
|
||
"""检索"""
|
||
pass
|
||
|
||
@abstractmethod
|
||
def search(self, query: str, limit: int = 10) -> List[Dict]:
|
||
"""搜索"""
|
||
pass
|
||
|
||
|
||
class FileBasedMemoryStore(MemoryStore):
|
||
"""
|
||
基于文件的长期记忆存储
|
||
|
||
特点:
|
||
- 持久化到磁盘
|
||
- JSON格式存储
|
||
- 按类别分目录
|
||
"""
|
||
|
||
def __init__(self, base_path: Union[str, Path]):
|
||
self.base_path = Path(base_path)
|
||
self.base_path.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 创建子目录
|
||
(self.base_path / "episodic").mkdir(exist_ok=True)
|
||
(self.base_path / "semantic").mkdir(exist_ok=True)
|
||
(self.base_path / "procedural").mkdir(exist_ok=True)
|
||
(self.base_path / "agent").mkdir(exist_ok=True)
|
||
|
||
def store(self, key: str, value: Any,
|
||
memory_type: str = "episodic",
|
||
metadata: Dict = None) -> bool:
|
||
"""存储记忆"""
|
||
try:
|
||
# 生成文件路径
|
||
safe_key = hashlib.md5(key.encode()).hexdigest()
|
||
file_path = self.base_path / memory_type / f"{safe_key}.json"
|
||
|
||
# 准备数据
|
||
data = {
|
||
"key": key,
|
||
"value": value,
|
||
"metadata": metadata or {},
|
||
"timestamp": time.time(),
|
||
"access_count": 0
|
||
}
|
||
|
||
# 如果文件存在,保留访问计数
|
||
if file_path.exists():
|
||
existing = json.loads(file_path.read_text())
|
||
data["access_count"] = existing.get("access_count", 0)
|
||
|
||
# 写入文件
|
||
file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"存储失败: {e}")
|
||
return False
|
||
|
||
def retrieve(self, key: str,
|
||
memory_type: str = "episodic") -> Optional[Any]:
|
||
"""检索记忆"""
|
||
safe_key = hashlib.md5(key.encode()).hexdigest()
|
||
file_path = self.base_path / memory_type / f"{safe_key}.json"
|
||
|
||
if not file_path.exists():
|
||
return None
|
||
|
||
try:
|
||
data = json.loads(file_path.read_text())
|
||
|
||
# 更新访问计数
|
||
data["access_count"] = data.get("access_count", 0) + 1
|
||
file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||
|
||
return data["value"]
|
||
|
||
except Exception:
|
||
return None
|
||
|
||
def search(self, query: str,
|
||
memory_type: str = None,
|
||
limit: int = 10) -> List[Dict]:
|
||
"""搜索记忆"""
|
||
results = []
|
||
|
||
# 确定搜索范围
|
||
if memory_type:
|
||
search_dirs = [self.base_path / memory_type]
|
||
else:
|
||
search_dirs = [
|
||
self.base_path / "episodic",
|
||
self.base_path / "semantic",
|
||
self.base_path / "procedural",
|
||
self.base_path / "agent"
|
||
]
|
||
|
||
# 搜索文件
|
||
for directory in search_dirs:
|
||
if not directory.exists():
|
||
continue
|
||
|
||
for file_path in directory.glob("*.json"):
|
||
try:
|
||
data = json.loads(file_path.read_text())
|
||
|
||
# 简单的关键词匹配
|
||
content = json.dumps(data, ensure_ascii=False).lower()
|
||
if query.lower() in content:
|
||
results.append({
|
||
"key": data.get("key"),
|
||
"value": data.get("value"),
|
||
"metadata": data.get("metadata", {}),
|
||
"timestamp": data.get("timestamp"),
|
||
"relevance": self._calculate_relevance(
|
||
query, content
|
||
)
|
||
})
|
||
|
||
except Exception:
|
||
continue
|
||
|
||
if len(results) >= limit:
|
||
break
|
||
|
||
# 按相关性排序
|
||
results.sort(key=lambda x: x.get("relevance", 0), reverse=True)
|
||
return results[:limit]
|
||
|
||
def _calculate_relevance(self, query: str, content: str) -> float:
|
||
"""计算相关性得分"""
|
||
query_words = set(query.lower().split())
|
||
content_words = set(content.lower().split())
|
||
|
||
if not query_words:
|
||
return 0
|
||
|
||
# Jaccard相似度
|
||
intersection = len(query_words & content_words)
|
||
union = len(query_words | content_words)
|
||
return intersection / union if union > 0 else 0
|
||
|
||
def list_all(self, memory_type: str = None) -> List[Dict]:
|
||
"""列出所有记忆"""
|
||
memories = []
|
||
|
||
if memory_type:
|
||
search_dirs = [self.base_path / memory_type]
|
||
else:
|
||
search_dirs = [
|
||
self.base_path / "episodic",
|
||
self.base_path / "semantic",
|
||
self.base_path / "procedural",
|
||
self.base_path / "agent"
|
||
]
|
||
|
||
for directory in search_dirs:
|
||
if not directory.exists():
|
||
continue
|
||
|
||
for file_path in directory.glob("*.json"):
|
||
try:
|
||
data = json.loads(file_path.read_text())
|
||
memories.append({
|
||
"key": data.get("key"),
|
||
"type": str(directory.relative_to(self.base_path)),
|
||
"timestamp": data.get("timestamp"),
|
||
"metadata": data.get("metadata", {})
|
||
})
|
||
except Exception:
|
||
continue
|
||
|
||
return memories
|
||
```
|
||
|
||
### 语义记忆与向量检索
|
||
|
||
```python
|
||
import numpy as np
|
||
from typing import List, Tuple
|
||
import pickle
|
||
|
||
|
||
class SemanticMemory:
|
||
"""
|
||
语义记忆:基于向量相似度的知识存储
|
||
|
||
特点:
|
||
- 使用嵌入向量表示语义
|
||
- 支持语义相似度搜索
|
||
- 适合存储领域知识和概念
|
||
"""
|
||
|
||
def __init__(self, embedding_dim: int = 768):
|
||
self.embedding_dim = embedding_dim
|
||
self.memories = [] # [(embedding, content, metadata), ...]
|
||
|
||
def add(self, content: str, embedding: np.ndarray,
|
||
metadata: Dict = None) -> None:
|
||
"""添加语义记忆"""
|
||
if embedding.shape != (self.embedding_dim,):
|
||
raise ValueError(f"嵌入维度不匹配,期望 {self.embedding_dim}")
|
||
|
||
self.memories.append({
|
||
"embedding": embedding,
|
||
"content": content,
|
||
"metadata": metadata or {}
|
||
})
|
||
|
||
def retrieve(self, query_embedding: np.ndarray,
|
||
top_k: int = 5,
|
||
threshold: float = 0.7) -> List[Dict]:
|
||
"""
|
||
检索最相关的记忆
|
||
|
||
Args:
|
||
query_embedding: 查询向量
|
||
top_k: 返回前k个结果
|
||
threshold: 相似度阈值
|
||
|
||
Returns:
|
||
相关记忆列表
|
||
"""
|
||
if not self.memories:
|
||
return []
|
||
|
||
# 计算余弦相似度
|
||
similarities = []
|
||
for memory in self.memories:
|
||
sim = self._cosine_similarity(
|
||
query_embedding,
|
||
memory["embedding"]
|
||
)
|
||
if sim >= threshold:
|
||
similarities.append((sim, memory))
|
||
|
||
# 排序并返回top-k
|
||
similarities.sort(key=lambda x: x[0], reverse=True)
|
||
return [
|
||
{
|
||
"content": mem["content"],
|
||
"metadata": mem["metadata"],
|
||
"similarity": sim
|
||
}
|
||
for sim, mem in similarities[:top_k]
|
||
]
|
||
|
||
def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
|
||
"""计算余弦相似度"""
|
||
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
|
||
|
||
def save(self, path: Union[str, Path]) -> None:
|
||
"""保存到文件"""
|
||
path = Path(path)
|
||
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
data = {
|
||
"embedding_dim": self.embedding_dim,
|
||
"memories": self.memories
|
||
}
|
||
path.write_bytes(pickle.dumps(data))
|
||
|
||
def load(self, path: Union[str, Path]) -> None:
|
||
"""从文件加载"""
|
||
path = Path(path)
|
||
if not path.exists():
|
||
return
|
||
|
||
data = pickle.loads(path.read_bytes())
|
||
self.embedding_dim = data["embedding_dim"]
|
||
self.memories = data["memories"]
|
||
|
||
|
||
class MockEmbeddingModel:
|
||
"""
|
||
模拟嵌入模型
|
||
|
||
实际实现中会使用真实的嵌入模型(如Sentence-BERT、OpenAI embeddings等)
|
||
"""
|
||
|
||
def __init__(self, dim: int = 768):
|
||
self.dim = dim
|
||
self.vocab = {} # 词到向量的映射
|
||
|
||
def encode(self, text: str) -> np.ndarray:
|
||
"""
|
||
编码文本为向量
|
||
|
||
这是一个简化的实现,实际会使用深度学习模型
|
||
"""
|
||
# 简单的词袋模型 + 随机投影
|
||
words = text.lower().split()
|
||
|
||
# 为新词创建向量
|
||
for word in words:
|
||
if word not in self.vocab:
|
||
self.vocab[word] = np.random.randn(self.dim) * 0.1
|
||
|
||
# 聚合词向量
|
||
vectors = [self.vocab[w] for w in words if w in self.vocab]
|
||
if vectors:
|
||
return np.mean(vectors, axis=0)
|
||
return np.zeros(self.dim)
|
||
```
|
||
|
||
### 程序记忆:技能与工作流
|
||
|
||
```python
|
||
class ProceduralMemory:
|
||
"""
|
||
程序记忆:存储技能和工作流程
|
||
|
||
特点:
|
||
- 存储可执行的技能
|
||
- 存储工作流模板
|
||
- 存储最佳实践
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.skills = {} # name: skill_definition
|
||
self.workflows = {} # name: workflow_definition
|
||
self.practices = {} # context: best_practice
|
||
|
||
def learn_skill(self, name: str,
|
||
input_spec: List[Dict],
|
||
output_spec: Dict,
|
||
implementation: str) -> None:
|
||
"""
|
||
学习新技能
|
||
|
||
Args:
|
||
name: 技能名称
|
||
input_spec: 输入规格列表
|
||
output_spec: 输出规格
|
||
implementation: 实现代码或描述
|
||
"""
|
||
self.skills[name] = {
|
||
"name": name,
|
||
"input_spec": input_spec,
|
||
"output_spec": output_spec,
|
||
"implementation": implementation,
|
||
"learned_at": time.time(),
|
||
"usage_count": 0
|
||
}
|
||
|
||
def learn_workflow(self, name: str,
|
||
steps: List[Dict],
|
||
description: str = "") -> None:
|
||
"""
|
||
学习工作流
|
||
|
||
Args:
|
||
name: 工作流名称
|
||
steps: 步骤列表
|
||
description: 描述
|
||
"""
|
||
self.workflows[name] = {
|
||
"name": name,
|
||
"steps": steps,
|
||
"description": description,
|
||
"learned_at": time.time(),
|
||
"usage_count": 0
|
||
}
|
||
|
||
def record_best_practice(self, context: str,
|
||
practice: str,
|
||
success_rate: float = 1.0) -> None:
|
||
"""
|
||
记录最佳实践
|
||
|
||
Args:
|
||
context: 应用场景
|
||
practice: 实践描述
|
||
success_rate: 成功率
|
||
"""
|
||
self.practices[context] = {
|
||
"practice": practice,
|
||
"success_rate": success_rate,
|
||
"recorded_at": time.time()
|
||
}
|
||
|
||
def get_workflow(self, name: str) -> Optional[Dict]:
|
||
"""获取工作流"""
|
||
if name in self.workflows:
|
||
self.workflows[name]["usage_count"] += 1
|
||
return self.workflows[name]
|
||
return None
|
||
|
||
def find_similar_workflow(self, goal: str) -> List[Dict]:
|
||
"""查找相似的工作流"""
|
||
results = []
|
||
for name, workflow in self.workflows.items():
|
||
# 简单的关键词匹配
|
||
if any(word in workflow.get("description", "").lower()
|
||
for word in goal.lower().split()):
|
||
results.append(workflow)
|
||
|
||
return sorted(
|
||
results,
|
||
key=lambda w: w["usage_count"],
|
||
reverse=True
|
||
)
|
||
|
||
def get_best_practice(self, context: str) -> Optional[str]:
|
||
"""获取最佳实践"""
|
||
if context in self.practices:
|
||
return self.practices[context]["practice"]
|
||
|
||
# 查找部分匹配
|
||
for key, value in self.practices.items():
|
||
if key in context or context in key:
|
||
return value["practice"]
|
||
|
||
return None
|
||
```
|
||
|
||
---
|
||
|
||
## 代码示例
|
||
|
||
### 专家知识积累系统
|
||
|
||
```python
|
||
"""
|
||
专家知识积累系统
|
||
|
||
演示如何构建一个完整的记忆系统,
|
||
支持短期和长期记忆,以及智能检索
|
||
"""
|
||
import time
|
||
import json
|
||
from pathlib import Path
|
||
from typing import List, Dict, Any, Optional
|
||
from dataclasses import dataclass, field
|
||
import hashlib
|
||
|
||
|
||
@dataclass
|
||
class ExpertKnowledge:
|
||
"""专家知识条目"""
|
||
topic: str
|
||
content: str
|
||
category: str
|
||
confidence: float = 0.8 # 0-1,置信度
|
||
source: str = ""
|
||
examples: List[str] = field(default_factory=list)
|
||
related_topics: List[str] = field(default_factory=list)
|
||
created_at: float = field(default_factory=time.time)
|
||
last_accessed: float = field(default_factory=time.time)
|
||
access_count: int = 0
|
||
|
||
|
||
class ExpertKnowledgeSystem:
|
||
"""
|
||
专家知识积累系统
|
||
|
||
功能:
|
||
1. 存储和检索领域知识
|
||
2. 跟踪知识使用情况
|
||
3. 发现知识关联
|
||
4. 生成知识摘要
|
||
"""
|
||
|
||
def __init__(self, storage_path: str = "./knowledge_base"):
|
||
self.storage_path = Path(storage_path)
|
||
self.storage_path.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 内存中的知识索引
|
||
self.knowledge_index: Dict[str, ExpertKnowledge] = {}
|
||
|
||
# 短期记忆缓存
|
||
self.recently_accessed: List[str] = []
|
||
|
||
# 加载已有知识
|
||
self._load_knowledge()
|
||
|
||
def add_knowledge(self,
|
||
topic: str,
|
||
content: str,
|
||
category: str,
|
||
confidence: float = 0.8,
|
||
source: str = "",
|
||
examples: List[str] = None,
|
||
related_topics: List[str] = None) -> bool:
|
||
"""添加知识条目"""
|
||
knowledge = ExpertKnowledge(
|
||
topic=topic,
|
||
content=content,
|
||
category=category,
|
||
confidence=confidence,
|
||
source=source,
|
||
examples=examples or [],
|
||
related_topics=related_topics or []
|
||
)
|
||
|
||
# 生成唯一ID
|
||
knowledge_id = self._generate_id(topic, category)
|
||
|
||
# 存储到索引
|
||
self.knowledge_index[knowledge_id] = knowledge
|
||
|
||
# 持久化
|
||
self._save_knowledge(knowledge_id, knowledge)
|
||
|
||
return True
|
||
|
||
def query(self, query: str,
|
||
category: str = None,
|
||
top_k: int = 5) -> List[Dict]:
|
||
"""
|
||
查询知识
|
||
|
||
Args:
|
||
query: 查询文本
|
||
category: 知识类别过滤
|
||
top_k: 返回结果数量
|
||
|
||
Returns:
|
||
匹配的知识条目列表
|
||
"""
|
||
results = []
|
||
query_lower = query.lower()
|
||
|
||
for knowledge_id, knowledge in self.knowledge_index.items():
|
||
# 类别过滤
|
||
if category and knowledge.category != category:
|
||
continue
|
||
|
||
# 计算相关性得分
|
||
score = self._calculate_relevance(query_lower, knowledge)
|
||
|
||
if score > 0:
|
||
results.append({
|
||
"id": knowledge_id,
|
||
"knowledge": knowledge,
|
||
"score": score
|
||
})
|
||
|
||
# 排序并返回top-k
|
||
results.sort(key=lambda x: x["score"], reverse=True)
|
||
|
||
# 更新访问记录
|
||
for result in results[:top_k]:
|
||
knowledge = result["knowledge"]
|
||
knowledge.access_count += 1
|
||
knowledge.last_accessed = time.time()
|
||
self.recently_accessed.append(result["id"])
|
||
|
||
return [
|
||
{
|
||
"topic": r["knowledge"].topic,
|
||
"content": r["knowledge"].content,
|
||
"category": r["knowledge"].category,
|
||
"confidence": r["knowledge"].confidence,
|
||
"relevance": r["score"],
|
||
"examples": r["knowledge"].examples
|
||
}
|
||
for r in results[:top_k]
|
||
]
|
||
|
||
def get_related_topics(self, topic: str) -> List[str]:
|
||
"""获取相关主题"""
|
||
related = set()
|
||
|
||
# 查找直接相关
|
||
for knowledge in self.knowledge_index.values():
|
||
if topic.lower() in knowledge.topic.lower():
|
||
related.update(knowledge.related_topics)
|
||
|
||
# 查找包含相同关键词的主题
|
||
topic_words = set(topic.lower().split())
|
||
for knowledge in self.knowledge_index.values():
|
||
knowledge_words = set(knowledge.topic.lower().split())
|
||
if topic_words & knowledge_words: # 有交集
|
||
related.add(knowledge.topic)
|
||
|
||
return list(related)
|
||
|
||
def generate_summary(self, category: str = None) -> str:
|
||
"""
|
||
生成知识库摘要
|
||
|
||
Args:
|
||
category: 指定类别,None则生成全部摘要
|
||
|
||
Returns:
|
||
知识库摘要文本
|
||
"""
|
||
knowledges = [
|
||
k for k in self.knowledge_index.values()
|
||
if category is None or k.category == category
|
||
]
|
||
|
||
if not knowledges:
|
||
return f"知识库中没有{'类别' + category if category else ''}的知识。"
|
||
|
||
# 按类别分组
|
||
by_category: Dict[str, List[ExpertKnowledge]] = {}
|
||
for k in knowledges:
|
||
if k.category not in by_category:
|
||
by_category[k.category] = []
|
||
by_category[k.category].append(k)
|
||
|
||
# 构建摘要
|
||
summary_parts = []
|
||
|
||
for cat, items in by_category.items():
|
||
summary_parts.append(f"\n## {cat.upper()}")
|
||
summary_parts.append(f"共 {len(items)} 条知识\n")
|
||
|
||
# 列出高频访问的知识
|
||
top_items = sorted(items, key=lambda x: x.access_count, reverse=True)[:5]
|
||
|
||
for item in top_items:
|
||
summary_parts.append(
|
||
f"- **{item.topic}** "
|
||
f"(置信度: {item.confidence:.2f}, "
|
||
f"访问: {item.access_count}次)"
|
||
)
|
||
summary_parts.append(f" {item.content[:100]}...")
|
||
|
||
return "\n".join(summary_parts)
|
||
|
||
def learn_from_interaction(self,
|
||
user_question: str,
|
||
agent_answer: str,
|
||
user_feedback: str = None) -> None:
|
||
"""
|
||
从交互中学习
|
||
|
||
Args:
|
||
user_question: 用户问题
|
||
agent_answer: Agent回答
|
||
user_feedback: 用户反馈
|
||
"""
|
||
# 分析问题,提取关键概念
|
||
topic = self._extract_topic(user_question)
|
||
|
||
# 如果得到正面反馈,将回答存为知识
|
||
if user_feedback and "good" in user_feedback.lower():
|
||
self.add_knowledge(
|
||
topic=topic,
|
||
content=f"问题: {user_question}\n答案: {agent_answer}",
|
||
category="qa",
|
||
confidence=0.7,
|
||
source="interaction"
|
||
)
|
||
|
||
def _calculate_relevance(self, query: str,
|
||
knowledge: ExpertKnowledge) -> float:
|
||
"""计算查询与知识的相关性"""
|
||
score = 0.0
|
||
|
||
# 主题匹配
|
||
if query in knowledge.topic.lower():
|
||
score += 1.0
|
||
|
||
# 内容匹配
|
||
query_words = set(query.split())
|
||
content_words = set(knowledge.content.lower().split())
|
||
|
||
if query_words & content_words:
|
||
intersection = len(query_words & content_words)
|
||
score += intersection * 0.1
|
||
|
||
# 类别匹配
|
||
if query in knowledge.category.lower():
|
||
score += 0.5
|
||
|
||
# 相关主题匹配
|
||
for related in knowledge.related_topics:
|
||
if query in related.lower():
|
||
score += 0.3
|
||
|
||
# 访问热度加成
|
||
score += min(knowledge.access_count * 0.01, 0.5)
|
||
|
||
# 置信度加权
|
||
score *= knowledge.confidence
|
||
|
||
return score
|
||
|
||
def _extract_topic(self, text: str) -> str:
|
||
"""从文本中提取主题(简化版)"""
|
||
# 简单实现:取前几个关键词
|
||
words = text.lower().split()
|
||
stop_words = {"what", "how", "where", "when", "why", "the", "a", "an", "is", "are"}
|
||
|
||
topic_words = [w for w in words[:5] if w not in stop_words and len(w) > 2]
|
||
return " ".join(topic_words[:3]) if topic_words else text[:30]
|
||
|
||
def _generate_id(self, topic: str, category: str) -> str:
|
||
"""生成知识ID"""
|
||
content = f"{topic}:{category}"
|
||
return hashlib.md5(content.encode()).hexdigest()[:16]
|
||
|
||
def _save_knowledge(self, knowledge_id: str,
|
||
knowledge: ExpertKnowledge) -> None:
|
||
"""持久化知识"""
|
||
category_dir = self.storage_path / knowledge.category
|
||
category_dir.mkdir(exist_ok=True)
|
||
|
||
file_path = category_dir / f"{knowledge_id}.json"
|
||
|
||
data = {
|
||
"topic": knowledge.topic,
|
||
"content": knowledge.content,
|
||
"category": knowledge.category,
|
||
"confidence": knowledge.confidence,
|
||
"source": knowledge.source,
|
||
"examples": knowledge.examples,
|
||
"related_topics": knowledge.related_topics,
|
||
"created_at": knowledge.created_at,
|
||
"last_accessed": knowledge.last_accessed,
|
||
"access_count": knowledge.access_count
|
||
}
|
||
|
||
file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||
|
||
def _load_knowledge(self) -> None:
|
||
"""加载已有知识"""
|
||
for category_dir in self.storage_path.iterdir():
|
||
if not category_dir.is_dir():
|
||
continue
|
||
|
||
for file_path in category_dir.glob("*.json"):
|
||
try:
|
||
data = json.loads(file_path.read_text())
|
||
knowledge = ExpertKnowledge(**data)
|
||
knowledge_id = self._generate_id(
|
||
knowledge.topic,
|
||
knowledge.category
|
||
)
|
||
self.knowledge_index[knowledge_id] = knowledge
|
||
except Exception as e:
|
||
print(f"加载知识失败 {file_path}: {e}")
|
||
|
||
|
||
# ==================== 演示程序 ====================
|
||
|
||
def demonstrate_knowledge_system():
|
||
"""演示专家知识系统"""
|
||
print("=" * 70)
|
||
print("专家知识积累系统演示")
|
||
print("=" * 70)
|
||
|
||
# 创建系统
|
||
system = ExpertiseKnowledgeSystem(storage_path="./demo_knowledge")
|
||
|
||
# 1. 添加生态学知识
|
||
print("\n1. 添加专家知识...")
|
||
system.add_knowledge(
|
||
topic="生态源地识别",
|
||
content="生态源地是生物物种生存、繁衍和扩散的核心区域。通常选择面积较大、连通性好、生态价值高的斑块作为源地。",
|
||
category="ecology",
|
||
confidence=0.95,
|
||
source="景观生态学",
|
||
examples=["MSPA分析", "形态空间格局分析"],
|
||
related_topics=["生态廊道", "阻力面", "MCR分析"]
|
||
)
|
||
|
||
system.add_knowledge(
|
||
topic="最小累积阻力模型",
|
||
content="MCR模型通过计算物种从源到目的地运动过程中克服阻力的最小累积成本来确定生态廊道。公式:MCR = f(min ΣDij × Rij)",
|
||
category="spatial_analysis",
|
||
confidence=0.9,
|
||
source="景观生态学",
|
||
examples=["廊道提取", "连通性分析"],
|
||
related_topics=["生态源地", "阻力面", "图算法"]
|
||
)
|
||
|
||
system.add_knowledge(
|
||
topic="NDVI植被指数",
|
||
content="归一化植被指数(NDVI) = (NIR - Red) / (NIR + Red),用于监测植被生长状况,范围-1到1,值越大表示植被越好。",
|
||
category="remote_sensing",
|
||
confidence=0.98,
|
||
source="遥感原理",
|
||
examples=["植被覆盖度", "生物量估算"],
|
||
related_topics=["EVI", "SAVI", "遥感指数"]
|
||
)
|
||
|
||
# 2. 查询知识
|
||
print("\n2. 查询知识('生态廊道')...")
|
||
results = system.query("生态廊道", top_k=3)
|
||
|
||
for i, result in enumerate(results, 1):
|
||
print(f"\n结果 {i}:")
|
||
print(f" 主题: {result['topic']}")
|
||
print(f" 类别: {result['category']}")
|
||
print(f" 相关性: {result['relevance']:.2f}")
|
||
print(f" 内容: {result['content'][:80]}...")
|
||
|
||
# 3. 获取相关主题
|
||
print("\n3. 获取相关主题('源地')...")
|
||
related = system.get_related_topics("源地")
|
||
print(f"相关主题: {related}")
|
||
|
||
# 4. 生成摘要
|
||
print("\n4. 生成知识库摘要...")
|
||
summary = system.generate_summary()
|
||
print(summary)
|
||
|
||
# 5. 从交互学习
|
||
print("\n5. 从交互中学习...")
|
||
system.learn_from_interaction(
|
||
user_question="如何判断一个斑块是否适合作为生态源地?",
|
||
agent_answer="判断生态源地主要考虑:1)面积阈值(通常大于核心区面积);2)形状指数(越紧凑越好);3)连通性(与其他源地的距离);4)生态价值(植被覆盖、物种丰富度)。",
|
||
user_feedback="good, this helps!"
|
||
)
|
||
|
||
print("\n学习后的新查询:")
|
||
results = system.query("判断斑块", category="qa")
|
||
for r in results:
|
||
print(f" - {r['topic']}: {r['content'][:60]}...")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
demonstrate_knowledge_system()
|
||
```
|
||
|
||
---
|
||
|
||
## 案例分析
|
||
|
||
### Claude Code的对话上下文管理
|
||
|
||
```python
|
||
class ConversationContext:
|
||
"""
|
||
Claude Code风格的对话上下文管理
|
||
|
||
特点:
|
||
- 维护完整对话历史
|
||
- 智能截断以控制token使用
|
||
- 保留关键信息
|
||
"""
|
||
|
||
def __init__(self, max_tokens: int = 8000):
|
||
self.max_tokens = max_tokens
|
||
self.messages = []
|
||
self.system_prompt = ""
|
||
self.key_facts = []
|
||
|
||
def add_message(self, role: str, content: str) -> None:
|
||
"""添加消息"""
|
||
self.messages.append({
|
||
"role": role,
|
||
"content": content,
|
||
"timestamp": time.time()
|
||
})
|
||
|
||
def get_context(self) -> str:
|
||
"""
|
||
获取当前上下文
|
||
|
||
策略:
|
||
1. 始终包含系统提示
|
||
2. 保留最近的消息
|
||
3. 如果空间允许,保留关键事实
|
||
"""
|
||
context_parts = []
|
||
|
||
# 系统提示
|
||
if self.system_prompt:
|
||
context_parts.append(f"System: {self.system_prompt}")
|
||
|
||
# 关键事实
|
||
if self.key_facts:
|
||
context_parts.append("\nKey Facts:")
|
||
for fact in self.key_facts:
|
||
context_parts.append(f"- {fact}")
|
||
|
||
# 最近的消息(在token预算内)
|
||
available_tokens = self.max_tokens - self._count_tokens("\n".join(context_parts))
|
||
|
||
recent_messages = self._get_recent_messages(available_tokens)
|
||
for msg in recent_messages:
|
||
role = msg["role"].capitalize()
|
||
context_parts.append(f"{role}: {msg['content']}")
|
||
|
||
return "\n\n".join(context_parts)
|
||
|
||
def extract_key_fact(self, content: str) -> None:
|
||
"""从内容中提取关键事实"""
|
||
# 简化实现:包含特定模式的句子
|
||
import re
|
||
|
||
patterns = [
|
||
r"用户希望.*",
|
||
r"目标是.*",
|
||
r"需要注意.*",
|
||
r"重要.*"
|
||
]
|
||
|
||
for pattern in patterns:
|
||
matches = re.findall(pattern, content)
|
||
self.key_facts.extend(matches)
|
||
|
||
def _count_tokens(self, text: str) -> int:
|
||
"""估算token数量"""
|
||
return len(text) // 4
|
||
|
||
def _get_recent_messages(self, max_tokens: int) -> List[Dict]:
|
||
"""获取最近的消息(在token限制内)"""
|
||
result = []
|
||
used_tokens = 0
|
||
|
||
for msg in reversed(self.messages):
|
||
tokens = self._count_tokens(msg["content"])
|
||
if used_tokens + tokens > max_tokens:
|
||
break
|
||
result.append(msg)
|
||
used_tokens += tokens
|
||
|
||
return list(reversed(result))
|
||
```
|
||
|
||
---
|
||
|
||
## 反思与延伸
|
||
|
||
### 思考问题
|
||
|
||
1. **记忆容量**:如何平衡记忆容量和检索效率?
|
||
|
||
2. **记忆更新**:如何处理过时或错误的记忆?
|
||
|
||
3. **隐私保护**:长期记忆中如何保护敏感信息?
|
||
|
||
4. **记忆遗忘**:是否应该模拟人类的遗忘机制?
|
||
|
||
### 延伸阅读
|
||
|
||
- **"Memory Systems"** (Atkinson & Shiffrin) - 记忆心理学模型
|
||
- **"Vector Databases for AI"** - 向量数据库技术
|
||
- RAG论文 - "Retrieval-Augmented Generation"
|
||
|
||
---
|
||
|
||
## 关键要点
|
||
|
||
1. **记忆分层次**:短期记忆快速访问,长期记忆持久存储
|
||
2. **语义记忆**使用向量嵌入支持语义相似度检索
|
||
3. **情景记忆**记录具体经验和历史事件
|
||
4. **程序记忆**存储技能和工作流程
|
||
5. **检索策略**需要平衡相关性、时效性和访问频率
|
||
6. **知识积累**可以通过从交互中学习来实现
|