Merge branch 'merge' of https://github.com/modelscope/MemoryScope into merge
3
.gitignore
vendored
|
|
@ -19,8 +19,6 @@ runs
|
|||
logs
|
||||
rag_nodes_index.jsonl
|
||||
alfworld_data
|
||||
beyondagent/dataset/appworld/data
|
||||
beyond*
|
||||
step_experiences/*
|
||||
build/*
|
||||
*.egg-info/*
|
||||
|
|
@ -29,4 +27,3 @@ cookbook/appworld/experiments/*
|
|||
cookbook/appworld/exp_result/*
|
||||
file_vector_store/*
|
||||
cookbook/appworld/file_vector_store/*
|
||||
experiencemaker/tool/web_search_cach/*
|
||||
270
README.md
|
|
@ -0,0 +1,270 @@
|
|||
# ReMe.ai
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/figure/logo.jpg" alt="ReMe.ai Logo" width="100%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://pypi.org/project/reme-ai/"><img src="https://img.shields.io/badge/python-3.12+-blue" alt="Python Version"></a>
|
||||
<a href="https://pypi.org/project/reme-ai/"><img src="https://img.shields.io/badge/pypi-v1.0.0-blue?logo=pypi" alt="PyPI Version"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="https://github.com/modelscope/ReMe.ai"><img src="https://img.shields.io/github/stars/modelscope/ReMe.ai?style=social" alt="GitHub Stars"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>记忆驱动的AI智能体框架</strong><br>
|
||||
<em>"如果说我比别人看得更远些,那是因为我站在了巨人的肩膀上。" —— 牛顿</em>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Remember Everyone, Recreate Everything
|
||||
|
||||
Remember Me, Reshape Me
|
||||
|
||||
Remember Me, Refine Me
|
||||
|
||||
Remember Me, Reinvent Me
|
||||
|
||||
今天的每个AI智能体都在从零开始。每当智能体处理任务时,它都在重新发明无数其他智能体已经发现的解决方案。这就像要求每个人都从头发现火、农业和数学一样。
|
||||
|
||||
ReMe.ai希望改变这一点。我们为AI智能体提供了统一的记忆与经验系统——在跨用户、跨任务、跨智能体下抽取、复用和分享记忆的能力。
|
||||
|
||||
```
|
||||
任务经验 (Task Memory) + 个人记忆 (Personal Memory) = agent的记忆管理
|
||||
```
|
||||
|
||||
个人记忆回答"**如何理解用户需要**",任务记忆回答"**如何做得更好**",
|
||||
|
||||
---
|
||||
|
||||
## 📰 最新动态
|
||||
- **[2025-09]** 🎉 ReMe.ai v1.0.0 正式发布,整合任务经验与个人记忆
|
||||
- **[2025-08]** 🚀 MCP协议支持已上线!→ [快速开始指南](./doc/mcp_quick_start.md)
|
||||
- **[2025-07]** 📚 完整文档和快速开始指南发布
|
||||
- **[2025-06]** 🚀 多后端向量存储支持 (Elasticsearch & ChromaDB)
|
||||
|
||||
---
|
||||
|
||||
## ✨ 架构设计
|
||||
|
||||
### 🎯 双模记忆系统
|
||||
|
||||
ReMe.ai整合两种互补的记忆能力:
|
||||
|
||||
#### 🧠 **任务经验 (Task Memory/Experience)**
|
||||
跨智能体复用的程序性知识
|
||||
- **成功模式识别**:识别有效策略并理解其根本原理
|
||||
- **失败分析学习**:从错误中学习,避免重复同样的问题
|
||||
- **规划策略**:不同问题类型的规划策略
|
||||
- **工具使用模式**:经过验证的有效工具使用方法
|
||||
- **标准操作流程**:经过验证的方法论和流程
|
||||
|
||||
你可以从[快速开始指南](./doc/task_memory_readme.md)了解更多如何使用task memory的方法
|
||||
|
||||
#### 👤 **个人记忆 (personal memory)**
|
||||
特定用户的情境化记忆
|
||||
- **个体偏好**:用户的习惯、偏好和交互风格
|
||||
- **情境适应**:基于时间和上下文的智能记忆管理
|
||||
- **渐进学习**:通过长期交互逐步建立深度理解
|
||||
- **时间感知**:检索和整合时都具备时间敏感性
|
||||
|
||||
- 你可以从[快速开始指南](./doc/personal_memory_readme.md)了解更多如何使用personal memory的方法
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 安装
|
||||
|
||||
### 从PyPI安装(推荐)
|
||||
```bash
|
||||
pip install reme-ai
|
||||
```
|
||||
|
||||
### 从源码安装
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ReMe.ai.git
|
||||
cd ReMe.ai
|
||||
pip install .
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
创建`.env`文件:
|
||||
```bash
|
||||
# 必需:LLM API配置
|
||||
LLM_API_KEY="sk-xxx"
|
||||
LLM_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# 必需:嵌入模型配置
|
||||
EMBEDDING_MODEL_API_KEY="sk-xxx"
|
||||
EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### HTTP服务启动
|
||||
```bash
|
||||
reme \
|
||||
backend=http \
|
||||
http.port=8001 \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
### MCP服务器支持
|
||||
```bash
|
||||
reme \
|
||||
backend=mcp \
|
||||
mcp.transport=stdio \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
### 核心API使用
|
||||
|
||||
#### 任务经验管理
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 经验总结器:从执行轨迹学习
|
||||
response = requests.post("http://localhost:8002/summary_task_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"trajectories": [
|
||||
{"messages": [{"role": "user", "content": "帮我制定项目计划"}], "score": 1.0}
|
||||
]
|
||||
})
|
||||
|
||||
# 经验检索器:获取相关经验
|
||||
response = requests.post("http://localhost:8002/retrieve_task_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"query": "如何高效管理项目进度?",
|
||||
"top_k": 1
|
||||
})
|
||||
```
|
||||
|
||||
#### 个人记忆管理
|
||||
```python
|
||||
# 记忆整合:从用户交互中学习
|
||||
response = requests.post("http://localhost:8002/summary_personal_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"trajectories": [
|
||||
{"messages":
|
||||
[
|
||||
{"role": "user", "content": "我喜欢早上喝咖啡工作"},
|
||||
{"role": "assistant", "content": "了解,您习惯早上用咖啡提神来开始工作"}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# 记忆检索:获取个人记忆片段
|
||||
response = requests.post("http://localhost:8002/retrieve_personal_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"query": "用户的工作习惯是什么?",
|
||||
"top_k": 5
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 实验结果
|
||||
|
||||
### Appworld基准测试
|
||||
使用qwen3-8b在Appworld上的测试结果:
|
||||
|
||||
| 方法 | pass@1 | pass@2 | pass@4 |
|
||||
|----------------------------|-----------|-------------|-----------|
|
||||
| 无记忆(基线) | 0.083 | 0.140 | 0.228 |
|
||||
| **使用任务经验** | **0.109** | **0.175** | **0.281** |
|
||||
|
||||
详见:[quickstart.md](cookbook/appworld/quickstart.md)
|
||||
|
||||
### FrozenLake实验
|
||||
使用qwen3-8b在100个随机FrozenLake地图上测试:
|
||||
|
||||
| 方法 | 通过率 |
|
||||
|---------------------------|-----------------|
|
||||
| 无记忆(基线) | 0.66 |
|
||||
| **使用任务经验** | 0.72 **(+9.1%)** |
|
||||
|
||||
| 无经验 | 有经验 |
|
||||
|:----------------------------------------------------------:|:---------------------------------------:|
|
||||
| <p align="center"><img src="doc/figure/frozenlake_failure.gif" alt="失败案例" width="30%"></p> | <p align="center"><img src="doc/figure/frozenlake_success.gif" alt="成功案例" width="30%"></p>
|
||||
|
||||
详见:[quickstart.md](cookbook/frozenlake/quickstart.md)
|
||||
|
||||
---
|
||||
|
||||
## 📦 即用型经验库
|
||||
|
||||
ReMe.ai提供预构建的经验库,智能体可以立即使用经过验证的最佳实践:
|
||||
|
||||
### 可用经验库
|
||||
- **`appworld_v1.jsonl`**:Appworld智能体交互的记忆库,涵盖复杂任务规划和执行模式
|
||||
- **`bfcl_v1.jsonl`**:BFCL工具调用的工作记忆库
|
||||
|
||||
### 快速使用
|
||||
```python
|
||||
# 加载预构建经验
|
||||
response = requests.post("http://localhost:8002/vector_store", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"action": "load",
|
||||
"path": "./library/"
|
||||
})
|
||||
|
||||
# 查询相关经验
|
||||
response = requests.post("http://localhost:8002/retrieve_task_memory", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"query": "如何导航到设置并更新用户资料?",
|
||||
"top_k": 1
|
||||
})
|
||||
```
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- **[快速开始](./cookbook/simple_demo/quick_start.md)**:通过实际示例快速上手
|
||||
- **[向量存储设置](./doc/vector_store_setup.md)**:生产部署指南
|
||||
- **[配置指南](./doc/configuration_guide.md)**:详细配置参考
|
||||
- **[操作文档](./doc/operations_documentation.md)**:操作配置说明
|
||||
- **[示例集合](./cookbook)**:实际用例和最佳实践
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
我们相信最好的记忆系统来自集体智慧。欢迎贡献:
|
||||
|
||||
### 代码贡献
|
||||
- 新操作和工具开发
|
||||
- 后端实现和优化
|
||||
- API增强和新端点
|
||||
|
||||
### 文档改进
|
||||
- 使用示例和教程
|
||||
- 最佳实践指南
|
||||
- 翻译和本地化
|
||||
|
||||
---
|
||||
|
||||
## 📄 引用
|
||||
|
||||
```bibtex
|
||||
@software{ReMe2025,
|
||||
title = {ReMe.ai: Memory-Driven AI Agent Framework},
|
||||
author = {The ReMe.ai Team},
|
||||
url = {https://github.com/modelscope/ReMe.ai},
|
||||
year = {2025}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ 许可证
|
||||
|
||||
本项目采用Apache License 2.0许可证 - 详情请参阅[LICENSE](./LICENSE)文件。
|
||||
|
||||
---
|
||||
277
README_ZH.md
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
# ReMe (formerly memoryscope)
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/figure/reme_logo.jpg" alt="ReMe.ai Logo" width="100%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://pypi.org/project/reme-ai/"><img src="https://img.shields.io/badge/python-3.12+-blue" alt="Python Version"></a>
|
||||
<a href="https://pypi.org/project/reme-ai/"><img src="https://img.shields.io/badge/pypi-v1.0.0-blue?logo=pypi" alt="PyPI Version"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="https://github.com/modelscope/ReMe"><img src="https://img.shields.io/github/stars/modelscope/ReMe?style=social" alt="GitHub Stars"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>ReMe: 为agent设计的记忆管理框架</strong><br>
|
||||
<em>Remember Me, Refine Me</em>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
agent时代的记忆不单是用于保存个性化的用户信息。agent在完成任务时,我们希望它能够有区分性地记住用户的偏好,以及如何正确地行动。
|
||||
|
||||
当智能体处理任务时,它都在重新发明无数其他智能体已经发现的解决方案。这就像要求每个人都从头发现火、农业和数学一样。
|
||||
|
||||
ReMe为AI智能体提供了统一的记忆与经验系统——在跨用户、跨任务、跨智能体下抽取、复用和分享记忆的能力。
|
||||
|
||||
```
|
||||
个性化记忆 (Personal Memory) + 任务经验 (Task Memory)= agent的记忆管理
|
||||
```
|
||||
|
||||
个性化记忆能够"**理解用户需要**",任务记忆让agent"**做得更好**",
|
||||
|
||||
---
|
||||
|
||||
## 📰 最新动态
|
||||
|
||||
- **[2025-09]** 🧪 我们在appworld, bfcl(v3) 以及frozenlake环境验证了记忆抽取与复用在agent中的效果,更多信息请查看 [appworld exp](./cookbook/appworld/quickstart.md), [bfcl exp](./cookbook/bfcl/quickstart.md) & [frozenlake exp](./cookbook/frozenlake/quickstart.md)
|
||||
- **[2025-09]** 🎉 ReMe(formerly [MemoryScope](./memoryscope/README.md)) v1.0 正式发布,整合任务经验与个人记忆。 如果想使用原始的memoryscope项目,你可以在[MemoryScope](./memoryscope)找到
|
||||
- **[2025-08]** 🚀 MCP协议支持已上线!→ [快速开始指南](./doc/mcp_quick_start.md)
|
||||
- **[2025-07]** 📚 完整文档和快速开始指南发布
|
||||
- **[2025-06]** 🚀 多后端向量存储支持 (Elasticsearch & ChromaDB) -> [快速开始指南](./doc/vector_store_api_guide.md)
|
||||
- **[2024-09]** 🧠 MemoryScope v0.1.1.0 发布,个性化和时间感知的记忆存储与使用
|
||||
|
||||
---
|
||||
|
||||
## ✨ 架构设计
|
||||
|
||||
### 🎯 双模记忆系统
|
||||
|
||||
ReMe整合两种互补的记忆能力:
|
||||
|
||||
#### 🧠 **任务经验 (Task Memory/Experience)**
|
||||
跨智能体复用的程序性知识
|
||||
- **成功模式识别**:识别有效策略并理解其根本原理
|
||||
- **失败分析学习**:从错误中学习,避免重复同样的问题
|
||||
- **对比模式**:不同采样轨迹通过对比得到更有价值的经验
|
||||
- **验证模式**:经过验证模块确认抽取记忆的有效性
|
||||
|
||||
你可以从[task memory](./doc/task_memory/task_memory.md)了解更多如何使用task memory的方法
|
||||
|
||||
#### 👤 **个人记忆 (personal memory)**
|
||||
特定用户的情境化记忆
|
||||
- **个体偏好**:用户的习惯、偏好和交互风格
|
||||
- **情境适应**:基于时间和上下文的智能记忆管理
|
||||
- **渐进学习**:通过长期交互逐步建立深度理解
|
||||
- **时间感知**:检索和整合时都具备时间敏感性
|
||||
|
||||
- 你可以从[personal](./doc/personal_memory/personal_memory.md)了解更多如何使用personal memory的方法
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 安装
|
||||
|
||||
### 从PyPI安装(推荐)
|
||||
```bash
|
||||
pip install reme-ai
|
||||
```
|
||||
|
||||
### 从源码安装
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ReMe.git
|
||||
cd ReMe
|
||||
pip install .
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
|
||||
复制 `example.env` 为 .env并修改其中对应参数:
|
||||
|
||||
```bash
|
||||
# 必需:LLM API配置
|
||||
FLOW_LLM_API_KEY=sk-xxxx
|
||||
FLOW_LLM_BASE_URL=https://xxxx/v1
|
||||
|
||||
# 必需:嵌入模型配置
|
||||
FLOW_EMBEDDING_API_KEY=sk-xxxx
|
||||
FLOW_EMBEDDING_BASE_URL=https://xxxx/v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### HTTP服务启动
|
||||
```bash
|
||||
reme \
|
||||
backend=http \
|
||||
http.port=8001 \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
### MCP服务器支持
|
||||
```bash
|
||||
reme \
|
||||
backend=mcp \
|
||||
mcp.transport=stdio \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
### 核心API使用
|
||||
|
||||
#### 任务经验管理
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 经验总结器:从执行轨迹学习
|
||||
response = requests.post("http://localhost:8002/summary_task_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"trajectories": [
|
||||
{"messages": [{"role": "user", "content": "帮我制定项目计划"}], "score": 1.0}
|
||||
]
|
||||
})
|
||||
|
||||
# 经验检索器:获取相关经验
|
||||
response = requests.post("http://localhost:8002/retrieve_task_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"query": "如何高效管理项目进度?",
|
||||
"top_k": 1
|
||||
})
|
||||
```
|
||||
|
||||
#### 个人记忆管理
|
||||
```python
|
||||
# 记忆整合:从用户交互中学习
|
||||
response = requests.post("http://localhost:8002/summary_personal_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"trajectories": [
|
||||
{"messages":
|
||||
[
|
||||
{"role": "user", "content": "我喜欢早上喝咖啡工作"},
|
||||
{"role": "assistant", "content": "了解,您习惯早上用咖啡提神来开始工作"}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# 记忆检索:获取个人记忆片段
|
||||
response = requests.post("http://localhost:8002/retrieve_personal_memory", json={
|
||||
"workspace_id": "task_workspace",
|
||||
"query": "用户的工作习惯是什么?",
|
||||
"top_k": 5
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 即用型经验库
|
||||
|
||||
ReMe提供预构建的经验库,智能体可以立即使用经过验证的最佳实践:
|
||||
|
||||
### 可用经验库
|
||||
- **`appworld_v1.jsonl`**:Appworld智能体交互的记忆库,涵盖复杂任务规划和执行模式
|
||||
- **`bfcl_v1.jsonl`**:BFCL工具调用的工作记忆库
|
||||
|
||||
### 快速使用
|
||||
```python
|
||||
# 加载预构建经验
|
||||
response = requests.post("http://localhost:8002/vector_store", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"action": "load",
|
||||
"path": "./library/"
|
||||
})
|
||||
|
||||
# 查询相关经验
|
||||
response = requests.post("http://localhost:8002/retrieve_task_memory", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"query": "如何导航到设置并更新用户资料?",
|
||||
"top_k": 1
|
||||
})
|
||||
```
|
||||
|
||||
## 🧪 实验
|
||||
|
||||
### 🌍 Appworld 实验
|
||||
|
||||
我们在 Appworld 上使用 qwen3-8b 测试 ReMe:
|
||||
|
||||
| 方法 | pass@1 | pass@2 | pass@4 |
|
||||
|---------------------|-----------|-------------|-----------|
|
||||
| 不使用 ReMe (baseline) | 0.083 | 0.140 | 0.228 |
|
||||
| **使用 ReMe** | | | |
|
||||
| w/ memory(直接使用) | **0.109** | **0.175** | **0.281** |
|
||||
|
||||
Pass@K 衡量的是在生成的 K 个样本中,至少有一个成功完成任务(score=1)的概率。
|
||||
当前实验使用的是一个内部的 AppWorld 环境,可能存在轻微差异。
|
||||
|
||||
你可以在 [quickstart.md](cookbook/appworld/quickstart.md) 中找到复现实验的更多细节。
|
||||
|
||||
|
||||
### 🧊 Frozenlake 实验
|
||||
|
||||
| 不使用memory | 使用memory |
|
||||
|:-------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------:|
|
||||
| <p align="center"><img src="doc/figure/frozenlake_failure.gif" alt="GIF 1" width="30%"></p> | <p align="center"><img src="doc/figure/frozenlake_success.gif" alt="GIF 2" width="30%"></p> |
|
||||
|
||||
我们在 100 个随机 frozenlake 地图上使用 qwen3-8b 进行测试:
|
||||
|
||||
| 方法 | pass rate |
|
||||
|---------------------|----------------|
|
||||
| 不使用 ReMe (baseline) | 0.66 |
|
||||
| **使用 ReMe** | |
|
||||
| w/ memory (直接使用) | 0.72 **(+9.1%)** |
|
||||
|
||||
你可以在 [quickstart.md](cookbook/frozenlake/quickstart.md) 中找到复现实验的更多细节。
|
||||
|
||||
### 🔧 BFCL-V3 实验
|
||||
|
||||
即将推出!请持续关注完整的评估结果。
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- **[快速开始](./cookbook/simple_demo)**:通过实际示例快速上手
|
||||
- **[向量存储设置](./doc/vector_store_api_guide.md)**:配置本地/向量数据库以及使用
|
||||
- **[mcp指南](./doc/mcp_quick_start.md)**:创建mcp服务
|
||||
- **链路说明**: 个性化记忆与任务记忆中分别使用的算子及其含义可以分别在 [personal memory](./doc/personal_memory) 与 [task memory](./doc/task_memory)中找到,你可以修改config以自定义链路
|
||||
- **[示例集合](./cookbook)**:实际用例和最佳实践
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
我们相信最好的记忆系统来自集体智慧。欢迎贡献:
|
||||
|
||||
### 代码贡献
|
||||
- 新操作和工具开发
|
||||
- 后端实现和优化
|
||||
- API增强和新端点
|
||||
|
||||
### 文档改进
|
||||
- 使用示例和教程
|
||||
- 最佳实践指南
|
||||
|
||||
---
|
||||
|
||||
## 📄 引用
|
||||
|
||||
```bibtex
|
||||
@software{ReMe2025,
|
||||
title = {ReMe: Memory Framework for AI Agent},
|
||||
author = {The ReMe Team},
|
||||
url = {https://github.com/modelscope/ReMe},
|
||||
year = {2025}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ 许可证
|
||||
|
||||
本项目采用Apache License 2.0许可证 - 详情请参阅[LICENSE](./LICENSE)文件。
|
||||
|
||||
---
|
||||
|
|
@ -36,9 +36,9 @@ class AppworldReactAgent:
|
|||
max_interactions: int = 30,
|
||||
max_response_size: int = 2048,
|
||||
num_runs: int = 1,
|
||||
use_experience: bool = False,
|
||||
make_experience: bool = False,
|
||||
exp_url: str = "http://0.0.0.0:8001/",
|
||||
use_task_memory: bool = False,
|
||||
make_task_memory: bool = False,
|
||||
api_url: str = "http://0.0.0.0:8002/",
|
||||
workspace_id: str="appworld_v1"):
|
||||
|
||||
self.index: int = index
|
||||
|
|
@ -49,9 +49,9 @@ class AppworldReactAgent:
|
|||
self.max_interactions: int = max_interactions
|
||||
self.max_response_size: int = max_response_size
|
||||
self.num_runs: int = num_runs
|
||||
self.use_experience: bool = use_experience
|
||||
self.make_experience: bool = make_experience
|
||||
self.exp_url = exp_url
|
||||
self.use_task_memory: bool = use_task_memory
|
||||
self.make_task_memory: bool = make_task_memory
|
||||
self.api_url = api_url
|
||||
self.workspace_id = workspace_id
|
||||
|
||||
self.llm_client = OpenAI()
|
||||
|
|
@ -75,10 +75,10 @@ class AppworldReactAgent:
|
|||
return "call llm error"
|
||||
|
||||
def prompt_messages(self,world: AppWorld) -> list[dict]:
|
||||
if self.use_experience:
|
||||
experience = self.get_experience(world.task.instruction)
|
||||
logger.info(f"loaded experience: {experience}")
|
||||
dictionary = {"supervisor": world.task.supervisor, "instruction": world.task.instruction, "experience": experience}
|
||||
if self.use_task_memory:
|
||||
task_memory = self.get_task_memory(world.task.instruction)
|
||||
logger.info(f"loaded task_memory: {task_memory}")
|
||||
dictionary = {"supervisor": world.task.supervisor, "instruction": world.task.instruction, "experience": task_memory}
|
||||
else:
|
||||
dictionary = {"supervisor": world.task.supervisor, "instruction": world.task.instruction ,"experience": ""}
|
||||
print(dictionary)
|
||||
|
|
@ -144,30 +144,75 @@ class AppworldReactAgent:
|
|||
}
|
||||
result.append(t_result)
|
||||
|
||||
if self.make_experience:
|
||||
self.make_experience(result)
|
||||
if self.make_task_memory:
|
||||
memory_list = self.make_task_memory(result)
|
||||
logger.info(f"Created {len(memory_list) if memory_list else 0} task memories")
|
||||
|
||||
return result
|
||||
|
||||
def get_experience(self, query: str):
|
||||
response = requests.post(url=self.exp_url + "retriever", json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"query": query,
|
||||
"top_k": 5
|
||||
})
|
||||
|
||||
def handle_api_response(self, response: requests.Response):
|
||||
"""Handle API response with proper error checking"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: {response.status_code}")
|
||||
print(response.text)
|
||||
return None
|
||||
|
||||
return response.json()
|
||||
|
||||
def get_task_memory(self, query: str):
|
||||
"""Retrieve relevant task memories based on a query"""
|
||||
response = requests.post(
|
||||
url=f"{self.api_url}retrieve_task_memory",
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"query": query,
|
||||
}
|
||||
)
|
||||
|
||||
result = self.handle_api_response(response)
|
||||
if not result:
|
||||
return ""
|
||||
|
||||
response = response.json()
|
||||
print(response)
|
||||
experience_merged: str = response["experience_merged"]
|
||||
print(f"experience_merged={experience_merged}")
|
||||
return experience_merged
|
||||
# Extract and return the answer
|
||||
answer = result.get("answer", "")
|
||||
print(f"Retrieved task memory: {answer}")
|
||||
return answer
|
||||
|
||||
def make_experience(self, result):
|
||||
pass
|
||||
def make_task_memory(self, result):
|
||||
"""Generate a summary of conversation messages and create task memories"""
|
||||
if not result:
|
||||
print("No results to summarize")
|
||||
return
|
||||
|
||||
# Prepare trajectories from results
|
||||
trajectories = []
|
||||
for r in result:
|
||||
if "task_history" in r:
|
||||
trajectories.append({
|
||||
"messages": r["task_history"],
|
||||
"score": float(r.get("uplift_score", 0.0))
|
||||
})
|
||||
|
||||
if not trajectories:
|
||||
print("No trajectories to summarize")
|
||||
return
|
||||
|
||||
response = requests.post(
|
||||
url=f"{self.api_url}summary_task_memory",
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"trajectories": trajectories
|
||||
}
|
||||
)
|
||||
|
||||
result = self.handle_api_response(response)
|
||||
if not result:
|
||||
return
|
||||
|
||||
# Extract memory list from response
|
||||
memory_list = result.get("metadata", {}).get("memory_list", [])
|
||||
print(f"Task memory list created: {len(memory_list)} memories")
|
||||
return memory_list
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# AppWorld Experiment Quick Start Guide
|
||||
|
||||
This guide helps you quickly set up and run AppWorld experiments with ExperienceMaker integration.
|
||||
This guide helps you quickly set up and run AppWorld experiments with ReMe integration.
|
||||
|
||||
## Env Setup
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ExperienceMaker.git
|
||||
cd ExperienceMaker/cookbook/appworld
|
||||
git clone https://github.com/modelscope/ReMe.git
|
||||
cd ReMe/cookbook/appworld
|
||||
```
|
||||
|
||||
### 2. Appworld Environment Setup
|
||||
|
|
@ -36,43 +36,43 @@ appworld download data
|
|||
|
||||
**Note**: The AppWorld data will be saved in the current directory.
|
||||
|
||||
### 3. Start ExperienceMaker Service
|
||||
### 3. Start ReMe Service
|
||||
|
||||
Install ExperienceMaker (if not already installed)
|
||||
If you haven't installed the ExperienceMaker environment yet, follow these steps:
|
||||
Install ReMe (if not already installed)
|
||||
If you haven't installed the ReMe environment yet, follow these steps:
|
||||
```bash
|
||||
# Go back to the project root
|
||||
cd ../..
|
||||
|
||||
# Create ExperienceMaker environment
|
||||
conda create -p ./em-env python==3.12
|
||||
conda activate ./em-env
|
||||
# Create ReMe environment
|
||||
conda create -p ./reme-env python==3.12
|
||||
conda activate ./reme-env
|
||||
|
||||
# Install ExperienceMaker
|
||||
# Install ReMe
|
||||
pip install .
|
||||
```
|
||||
|
||||
Launch the ExperienceMaker service to enable experience library functionality:
|
||||
Launch the ReMe service to enable memory library functionality:
|
||||
|
||||
```bash
|
||||
experiencemaker \
|
||||
reme \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen-max-latest \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
```
|
||||
|
||||
add experiences for appworld:
|
||||
add memories for appworld:
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "appworld_v1",
|
||||
"action": "dump",
|
||||
"path": "./experience_library"
|
||||
"path": "./memory_library"
|
||||
}'
|
||||
```
|
||||
Now you have loaded the ExperienceMaker experience library to enable experience-based agent!
|
||||
Now you have loaded the ReMe memory library to enable memory-based agent!
|
||||
|
||||
### 4. Common Issues
|
||||
|
||||
|
|
@ -84,9 +84,9 @@ Now you have loaded the ExperienceMaker experience library to enable experience-
|
|||
|
||||
## Run Experiments
|
||||
|
||||
### 1. Test: With Experience vs Without Experience
|
||||
### 1. Test: With Memory vs Without Memory
|
||||
|
||||
Run the main experiment script to compare performance with and without experience:
|
||||
Run the main experiment script to compare performance with and without memory:
|
||||
|
||||
```bash
|
||||
python run_appworld.py
|
||||
|
|
@ -94,7 +94,7 @@ python run_appworld.py
|
|||
|
||||
**What this does:**
|
||||
- Runs AppWorld tasks on the development dataset
|
||||
- Compares agent performance with experience (`use_experience=True`) vs without experience
|
||||
- Compares agent performance with ReMe memory (`use_memory=True`) vs without memory
|
||||
- Uses multiple workers for parallel processing
|
||||
- Runs each task multiple times for statistical significance
|
||||
- Results are automatically saved to `./exp_result/` directory
|
||||
|
|
@ -102,7 +102,7 @@ python run_appworld.py
|
|||
**Configuration options in `run_appworld.py`:**
|
||||
- `max_workers`: Number of parallel workers (default: 6)
|
||||
- `num_runs`: Number of times each task is repeated (default: 4)
|
||||
- `use_experience`: Whether to use ExperienceMaker experience library
|
||||
- `use_memory`: Whether to use ReMe memory library
|
||||
|
||||
### 2. View Experiment Results
|
||||
|
||||
|
|
@ -131,10 +131,10 @@ python run_exp_statistic.py
|
|||
## Understanding Results
|
||||
|
||||
The experiment compares:
|
||||
1. **Baseline**: Agent without experience library
|
||||
2. **With Experience**: Agent enhanced with ExperienceMaker experience library
|
||||
1. **Baseline**: Agent without memory library
|
||||
2. **With Memory**: Agent enhanced with ReMe memory library
|
||||
|
||||
Key metrics to look for:
|
||||
- **best@1**: Average performance across all single runs
|
||||
- **best@k**: Performance when taking the best of k attempts
|
||||
- Improvement percentage when using experience vs baseline
|
||||
- Improvement percentage when using memory vs baseline
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import time
|
||||
import requests
|
||||
|
||||
import ray
|
||||
from ray import logger
|
||||
|
|
@ -17,7 +18,64 @@ from appworld import load_task_ids
|
|||
from appworld_react_agent import AppworldReactAgent
|
||||
|
||||
|
||||
def run_agent(dataset_name: str, experiment_suffix: str, max_workers: int, num_runs: int = 1, use_experience: bool = False, workspace_id: str="appworld", exp_url: str = "http://0.0.0.0:8001/") :
|
||||
def handle_api_response(response: requests.Response):
|
||||
"""Handle API response with proper error checking"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: {response.status_code}")
|
||||
print(response.text)
|
||||
return None
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def delete_workspace(workspace_id: str, api_url: str = "http://0.0.0.0:8002/"):
|
||||
"""Delete the current workspace from the vector store"""
|
||||
response = requests.post(
|
||||
url=f"{api_url}vector_store",
|
||||
json={
|
||||
"workspace_id": workspace_id,
|
||||
"action": "delete",
|
||||
}
|
||||
)
|
||||
|
||||
result = handle_api_response(response)
|
||||
if result:
|
||||
print(f"Workspace '{workspace_id}' deleted successfully")
|
||||
|
||||
|
||||
def dump_memory(workspace_id: str, path: str = "./", api_url: str = "http://0.0.0.0:8002/"):
|
||||
"""Dump the vector store memories to disk"""
|
||||
response = requests.post(
|
||||
url=f"{api_url}vector_store",
|
||||
json={
|
||||
"workspace_id": workspace_id,
|
||||
"action": "dump",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
|
||||
result = handle_api_response(response)
|
||||
if result:
|
||||
print(f"Memory dumped to {path}")
|
||||
|
||||
|
||||
def load_memory(workspace_id: str, path: str = "./", api_url: str = "http://0.0.0.0:8002/"):
|
||||
"""Load memories from disk into the vector store"""
|
||||
response = requests.post(
|
||||
url=f"{api_url}vector_store",
|
||||
json={
|
||||
"workspace_id": workspace_id,
|
||||
"action": "load",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
|
||||
result = handle_api_response(response)
|
||||
if result:
|
||||
print(f"Memory loaded from {path}")
|
||||
|
||||
|
||||
def run_agent(dataset_name: str, experiment_suffix: str, max_workers: int, num_runs: int = 1, use_task_memory: bool = False, make_task_memory: bool = False, workspace_id: str="appworld_v1", api_url: str = "http://0.0.0.0:8002/") :
|
||||
experiment_name = dataset_name + "_" + experiment_suffix
|
||||
path: Path = Path(f"./exp_result")
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -39,9 +97,10 @@ def run_agent(dataset_name: str, experiment_suffix: str, max_workers: int, num_r
|
|||
task_ids=worker_task_ids,
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=use_experience,
|
||||
use_task_memory=use_task_memory,
|
||||
make_task_memory=make_task_memory,
|
||||
workspace_id=workspace_id,
|
||||
exp_url=exp_url)
|
||||
api_url=api_url)
|
||||
future = actor.execute.remote()
|
||||
future_list.append(future)
|
||||
time.sleep(1)
|
||||
|
|
@ -64,7 +123,10 @@ def run_agent(dataset_name: str, experiment_suffix: str, max_workers: int, num_r
|
|||
task_ids=[task_id],
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=use_experience)
|
||||
use_task_memory=use_task_memory,
|
||||
make_task_memory=make_task_memory,
|
||||
workspace_id=workspace_id,
|
||||
api_url=api_url)
|
||||
task_results = agent.execute()
|
||||
if isinstance(task_results, list):
|
||||
result.extend(task_results)
|
||||
|
|
@ -75,18 +137,43 @@ def run_agent(dataset_name: str, experiment_suffix: str, max_workers: int, num_r
|
|||
|
||||
def main():
|
||||
max_workers = 8
|
||||
num_runs = 1 # Run each task 4 times
|
||||
num_runs = 1 # Run each task once
|
||||
workspace_id = "appworld_v1"
|
||||
api_url = "http://0.0.0.0:8002/"
|
||||
|
||||
if max_workers > 1:
|
||||
ray.init(num_cpus=8)
|
||||
|
||||
logger.info("Start running experiments without experience")
|
||||
|
||||
# Clean up workspace before starting
|
||||
logger.info("Deleting workspace...")
|
||||
delete_workspace(workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
# First run to build task memories
|
||||
logger.info("Start running experiments to build task memories")
|
||||
run_agent(dataset_name="dev", experiment_suffix="build-memory",
|
||||
max_workers=max_workers, num_runs=1,
|
||||
use_task_memory=False, make_task_memory=True,
|
||||
workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
# Dump memories to disk for persistence
|
||||
logger.info("Dumping memories to disk...")
|
||||
dump_memory(workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
# Run experiments without task memory
|
||||
logger.info("Start running experiments without task memory")
|
||||
for i in range(num_runs):
|
||||
run_agent(dataset_name="dev", experiment_suffix=f"no-exp", max_workers=max_workers, num_runs=1,
|
||||
use_experience=False, workspace_id="appworld_v1")
|
||||
run_agent(dataset_name="dev", experiment_suffix=f"no-memory",
|
||||
max_workers=max_workers, num_runs=1,
|
||||
use_task_memory=False, make_task_memory=False,
|
||||
workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
logger.info("Start running experiments with experience")
|
||||
# Run experiments with task memory
|
||||
logger.info("Start running experiments with task memory")
|
||||
for i in range(num_runs):
|
||||
run_agent(dataset_name="dev", experiment_suffix=f"add-exp", max_workers=max_workers, num_runs=1, use_experience=True,workspace_id="appworld_v1")
|
||||
run_agent(dataset_name="dev", experiment_suffix=f"with-memory",
|
||||
max_workers=max_workers, num_runs=1,
|
||||
use_task_memory=True, make_task_memory=False,
|
||||
workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class GameResult:
|
|||
|
||||
@ray.remote
|
||||
class FrozenLakeReactAgent:
|
||||
"""A ReAct Agent for FrozenLake game with experience learning."""
|
||||
"""A ReAct Agent for FrozenLake game with task memory learning."""
|
||||
|
||||
def __init__(self,
|
||||
index: int,
|
||||
|
|
@ -42,8 +42,8 @@ class FrozenLakeReactAgent:
|
|||
temperature: float = 0.7,
|
||||
max_steps: int = 50,
|
||||
num_runs: int = 1,
|
||||
use_experience: bool = False,
|
||||
make_experience: bool = False):
|
||||
use_task_memory: bool = False,
|
||||
make_task_memory: bool = False):
|
||||
|
||||
self.index = index
|
||||
self.task_configs = task_configs
|
||||
|
|
@ -52,8 +52,8 @@ class FrozenLakeReactAgent:
|
|||
self.temperature = temperature
|
||||
self.max_steps = max_steps
|
||||
self.num_runs = num_runs
|
||||
self.use_experience = use_experience
|
||||
self.make_experience = make_experience
|
||||
self.use_task_memory = use_task_memory
|
||||
self.make_task_memory = make_task_memory
|
||||
|
||||
self.llm_client = OpenAI()
|
||||
self.action_map = {0: "LEFT", 1: "DOWN", 2: "RIGHT", 3: "UP"}
|
||||
|
|
@ -121,35 +121,34 @@ class FrozenLakeReactAgent:
|
|||
else:
|
||||
return self.prompts["frozenlake_sys_prompt_no_slippery"]
|
||||
|
||||
def get_experience(self, map_desc: str, is_slippery: bool) -> str:
|
||||
"""Retrieve relevant experience from experience service"""
|
||||
if not self.use_experience:
|
||||
def get_task_memory(self, map_desc: str, is_slippery: bool) -> str:
|
||||
"""Retrieve relevant task memory from task memory service"""
|
||||
if not self.use_task_memory:
|
||||
return ""
|
||||
|
||||
try:
|
||||
query = f"FrozenLake game map: {map_desc}, slippery: {is_slippery}"
|
||||
base_url = "http://0.0.0.0:8001/"
|
||||
base_url = "http://0.0.0.0:8002/"
|
||||
workspace_id = self.experiment_name
|
||||
|
||||
response = requests.post(
|
||||
url=base_url + "retriever",
|
||||
url=base_url + "retrieve_task_memory",
|
||||
json={
|
||||
"workspace_id": workspace_id,
|
||||
"query": query,
|
||||
"top_k": 3
|
||||
},
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get("experience_merged", "")
|
||||
return data.get("answer", "")
|
||||
else:
|
||||
logger.warning(f"Experience retrieval failed: {response.status_code}")
|
||||
logger.warning(f"Task memory retrieval failed: {response.status_code}")
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get experience: {e}")
|
||||
logger.warning(f"Failed to get task memory: {e}")
|
||||
return ""
|
||||
|
||||
def action_parser(self, response: str) -> int:
|
||||
|
|
@ -194,19 +193,19 @@ class FrozenLakeReactAgent:
|
|||
|
||||
env = gym.make("FrozenLake-v1", **env_kwargs)
|
||||
|
||||
# Get map description for experience
|
||||
# Get map description for task memory
|
||||
map_str = '\n'.join([''.join([cell.decode('utf-8') for cell in row])
|
||||
for row in env.unwrapped.desc])
|
||||
|
||||
# Build messages
|
||||
system_prompt = self.build_system_prompt(is_slippery)
|
||||
experience = self.get_experience(map_str, is_slippery)
|
||||
task_memory = self.get_task_memory(map_str, is_slippery)
|
||||
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
|
||||
if experience:
|
||||
exp_content = f"Here are some relevant tips from previous successful games:\n\n{experience}\n\nUse these tips to help you succeed."
|
||||
messages.append({"role": "user", "content": exp_content})
|
||||
if task_memory:
|
||||
memory_content = f"Here are some relevant tips from previous successful games:\n\n{task_memory}\n\nUse these tips to help you succeed."
|
||||
messages.append({"role": "user", "content": memory_content})
|
||||
messages.append(
|
||||
{"role": "assistant", "content": "I'll use these tips to navigate the frozen lake successfully."})
|
||||
|
||||
|
|
@ -279,56 +278,54 @@ class FrozenLakeReactAgent:
|
|||
"map_id": map_id,
|
||||
"is_slippery": is_slippery,
|
||||
"map_size": map_size,
|
||||
"use_experience": self.use_experience
|
||||
"use_task_memory": self.use_task_memory
|
||||
}
|
||||
)
|
||||
|
||||
return result, messages
|
||||
|
||||
def save_experience(self, results: List[GameResult], messages_list: List[List[Dict]]):
|
||||
"""Save successful trajectories as experience"""
|
||||
if not self.make_experience:
|
||||
def save_task_memory(self, results: List[GameResult], messages_list: List[List[Dict]]):
|
||||
"""Save successful trajectories as task memory"""
|
||||
if not self.make_task_memory:
|
||||
return
|
||||
|
||||
trajs = []
|
||||
trajectories = []
|
||||
for result, messages in zip(results, messages_list):
|
||||
if result.success:
|
||||
# Create trajectory for experience service
|
||||
# Create trajectory for task memory service
|
||||
traj = {
|
||||
"messages": messages,
|
||||
"query" : result.map_config["map_desc"],
|
||||
"score": 1.0, # Success
|
||||
}
|
||||
trajs.append(traj)
|
||||
trajectories.append(traj)
|
||||
else:
|
||||
traj = {
|
||||
"messages": messages,
|
||||
"query" : result.map_config["map_desc"],
|
||||
"score": 0.0, # Success
|
||||
"score": 0.0, # Failure
|
||||
}
|
||||
trajs.append(traj)
|
||||
trajectories.append(traj)
|
||||
|
||||
if trajs:
|
||||
if trajectories:
|
||||
try:
|
||||
base_url = "http://0.0.0.0:8001/"
|
||||
base_url = "http://0.0.0.0:8002/"
|
||||
workspace_id = self.experiment_name
|
||||
|
||||
response = requests.post(
|
||||
url=base_url + "summarizer",
|
||||
url=base_url + "summary_task_memory",
|
||||
json={
|
||||
"workspace_id": workspace_id,
|
||||
"traj_list": trajs
|
||||
"trajectories": trajectories
|
||||
},
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info(f"Saved {len(trajs)} trajectories as experience")
|
||||
logger.info(f"Saved {len(trajectories)} trajectories as task memory")
|
||||
else:
|
||||
logger.warning(f"Failed to save experience: {response.status_code}")
|
||||
logger.warning(f"Failed to save task memory: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving experience: {e}")
|
||||
logger.error(f"Error saving task memory: {e}")
|
||||
|
||||
def execute(self) -> List[Dict]:
|
||||
"""Execute all tasks"""
|
||||
|
|
@ -357,9 +354,9 @@ class FrozenLakeReactAgent:
|
|||
}
|
||||
all_results[-1] = result_dict
|
||||
|
||||
# Save experience if needed
|
||||
if self.make_experience:
|
||||
# Convert back to GameResult objects for experience saving
|
||||
# Save task memory if needed
|
||||
if self.make_task_memory:
|
||||
# Convert back to GameResult objects for task memory saving
|
||||
game_results = []
|
||||
for i, result_dict in enumerate(all_results):
|
||||
game_result = GameResult(
|
||||
|
|
@ -374,6 +371,6 @@ class FrozenLakeReactAgent:
|
|||
)
|
||||
game_results.append(game_result)
|
||||
|
||||
self.save_experience(game_results, all_messages)
|
||||
self.save_task_memory(game_results, all_messages)
|
||||
|
||||
return all_results
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
# FrozenLake Experiment Quick Start Guide
|
||||
|
||||
This guide helps you quickly set up and run FrozenLake experiments with ExperienceMaker integration.
|
||||
This guide helps you quickly set up and run FrozenLake experiments with ReMe integration. The FrozenLake experiment demonstrates how task memory can improve an agent's performance in a navigation task.
|
||||
|
||||
## Env Setup
|
||||
## Environment Setup
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ExperienceMaker.git
|
||||
cd ExperienceMaker/cookbook/frozenlake
|
||||
git clone https://github.com/modelscope/ReMe.git
|
||||
cd ReMe/cookbook/frozenlake
|
||||
```
|
||||
|
||||
### 2. FrozenLake Environment Setup
|
||||
|
|
@ -19,49 +19,47 @@ Install Gymnasium for FrozenLake environment:
|
|||
pip install gymnasium
|
||||
```
|
||||
|
||||
### 3. Start ExperienceMaker Service
|
||||
This will install:
|
||||
- gymnasium - for the FrozenLake environment
|
||||
- ray - for parallel execution
|
||||
- openai - for LLM API access
|
||||
- other dependencies
|
||||
|
||||
Install ExperienceMaker (if not already installed)
|
||||
If you haven't installed the ExperienceMaker environment yet, follow these steps:
|
||||
### 3. Start ReMe Service
|
||||
|
||||
If you haven't installed ReMe yet, follow these steps:
|
||||
```bash
|
||||
# Go back to the project root
|
||||
cd ../..
|
||||
|
||||
# Create ExperienceMaker environment
|
||||
conda create -p ./em-env python==3.12
|
||||
conda activate ./em-env
|
||||
# Create a virtual environment (optional)
|
||||
conda create -p ./reme-env python==3.10
|
||||
conda activate ./reme-env
|
||||
|
||||
# Install ExperienceMaker
|
||||
# Install ReMe
|
||||
pip install .
|
||||
```
|
||||
|
||||
Launch the ExperienceMaker service to enable experience library functionality:
|
||||
Launch the ReMe service to enable memory library functionality:
|
||||
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen-max-latest \
|
||||
reme \
|
||||
backend=http \
|
||||
http.port=8002 \
|
||||
llm.default.model_name=qwen-max-2025-01-25 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
Load default experience library for FrozenLake:
|
||||
Load default memory library for FrozenLake:
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "frozenlake_no_slippery",
|
||||
"action": "dump",
|
||||
"path": "./experience_library"
|
||||
}'
|
||||
```
|
||||
Now you have loaded the default FrozenLake experience library to enable experience-based agent!
|
||||
|
||||
## Run Experiments
|
||||
|
||||
### 1. Quick Test: Performance Evaluation Only (Default)
|
||||
|
||||
Run the main experiment script to test agent performance using existing experience:
|
||||
Run the main experiment script to test agent performance using existing memory:
|
||||
|
||||
```bash
|
||||
python run_frozenlake.py
|
||||
|
|
@ -69,54 +67,33 @@ python run_frozenlake.py
|
|||
|
||||
**What this does:**
|
||||
- Tests the agent on randomly generated FrozenLake maps
|
||||
- Uses the default experience library (`frozenlake_no_slippery`)
|
||||
- Uses the default memory library (`frozenlake_no_slippery`)
|
||||
- Evaluates performance with multiple runs for statistical significance
|
||||
- Results are automatically saved to `./exp_result/` directory
|
||||
|
||||
### 2. Advanced: Training + Testing (Experience Generation)
|
||||
### 2. Advanced: Training + Testing (Memory Generation)
|
||||
|
||||
To create new experiences through training and then test performance:
|
||||
To create new memories through training and then test performance:
|
||||
|
||||
```bash
|
||||
python run_frozenlake.py --enable-training
|
||||
You can modify the experiment parameters directly in the `run_frozenlake.py` file. The main parameters are in the `main()` function:
|
||||
|
||||
```python
|
||||
def main():
|
||||
experiment_name = "frozenlake_no_slippery" # Name of the experiment
|
||||
max_workers = 4 # Number of parallel workers
|
||||
training_runs = 4 # Runs per training map
|
||||
num_training_maps = 50 # Number of maps for training
|
||||
test_runs = 1 # Runs per test configuration
|
||||
num_test_maps = 100 # Number of test maps
|
||||
is_slippery = False # Enable slippery mode
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- **Stage 1 (Training)**: Generates new experiences by solving training maps
|
||||
- **Stage 2 (Testing)**: Evaluates performance using the generated experiences
|
||||
- Compares baseline performance vs experience-enhanced performance
|
||||
Key parameters to consider:
|
||||
- `experiment_name`: Used as the workspace ID for task memory
|
||||
- `is_slippery`: When True, agent movement becomes stochastic (harder)
|
||||
- `max_workers`: Increase for faster execution on multi-core systems
|
||||
|
||||
### 3. Custom Configuration Examples
|
||||
|
||||
**Basic customization:**
|
||||
```bash
|
||||
python run_frozenlake.py --experiment-name "my_frozenlake_test" --max-workers 8
|
||||
```
|
||||
|
||||
**Enable slippery mode:**
|
||||
```bash
|
||||
python run_frozenlake.py --slippery --experiment-name "frozenlake_slippery"
|
||||
```
|
||||
|
||||
**Full training experiment:**
|
||||
```bash
|
||||
python run_frozenlake.py \
|
||||
--enable-training \
|
||||
--experiment-name "frozenlake_training_experiment" \
|
||||
--max-workers 8 \
|
||||
--training-runs 4 \
|
||||
--num-training-maps 50 \
|
||||
--test-runs 5 \
|
||||
--num-test-maps 100 \
|
||||
--slippery
|
||||
```
|
||||
|
||||
**View all available options:**
|
||||
```bash
|
||||
python run_frozenlake.py --help
|
||||
```
|
||||
|
||||
### 4. View Experiment Results
|
||||
### 3. View Experiment Results
|
||||
|
||||
After running experiments, analyze the statistical results:
|
||||
|
||||
|
|
@ -128,30 +105,53 @@ python run_exp_statistic.py
|
|||
- Processes all result files in `./exp_result/`
|
||||
- Calculates success rates and performance metrics
|
||||
- Generates a summary table showing performance comparisons
|
||||
- Saves results to `experiment_summary.csv`
|
||||
- Analyzes the effect of task memory on performance
|
||||
- Saves results to `frozenlake_summary.csv`
|
||||
|
||||
## Configuration Parameters
|
||||
## Understanding the Implementation
|
||||
|
||||
| Parameter | Default Value | Description |
|
||||
|-----------|---------------|-------------|
|
||||
| `--experiment-name` | `frozenlake_no_slippery` | Name of the experiment |
|
||||
| `--max-workers` | `4` | Number of parallel workers |
|
||||
| `--enable-training` | `False` | Enable training phase (experience generation) |
|
||||
| `--training-runs` | `4` | Number of runs per training map |
|
||||
| `--num-training-maps` | `50` | Number of training maps |
|
||||
| `--test-runs` | `1` | Number of runs per test configuration |
|
||||
| `--num-test-maps` | `100` | Number of test maps to use |
|
||||
| `--slippery` | `False` | Enable slippery ice mode |
|
||||
### Key Components
|
||||
|
||||
1. **FrozenLakeReactAgent** (`frozenlake_react_agent.py`)
|
||||
- Implements a ReAct agent that interacts with the FrozenLake environment
|
||||
- Handles task memory retrieval and storage
|
||||
- Uses LLM (via OpenAI API) for decision making
|
||||
|
||||
2. **Experiment Runner** (`run_frozenlake.py`)
|
||||
- Manages the overall experiment flow
|
||||
- Handles training and testing phases
|
||||
- Uses Ray for parallel execution
|
||||
|
||||
3. **Map Manager** (`map_manager.py`)
|
||||
- Generates and manages test maps
|
||||
- Ensures consistent evaluation across experiments
|
||||
|
||||
4. **Statistics Analyzer** (`run_exp_statistic.py`)
|
||||
- Processes experiment results
|
||||
- Calculates performance metrics
|
||||
- Generates comparative analysis
|
||||
|
||||
## Understanding Results
|
||||
|
||||
The experiment evaluates agent performance on FrozenLake maps:
|
||||
|
||||
- **Success Rate**: Percentage of episodes that reach the goal
|
||||
- **Default Mode**: Uses existing experience library for quick testing
|
||||
- **Training Mode**: Generates new experiences then tests performance improvement
|
||||
- **Success Rate**: Percentage of episodes where the agent reaches the goal
|
||||
- **With vs. Without Memory**: Compares performance with and without task memory
|
||||
- **Slippery vs. Non-slippery**: Compares performance in different environment dynamics
|
||||
|
||||
**Output Files:**
|
||||
- `./exp_result/*.jsonl`: Raw experiment results
|
||||
- `./exp_result/experiment_summary.csv`: Statistical summary
|
||||
- Console output: Real-time progress and metrics
|
||||
### Output Files
|
||||
|
||||
- `./exp_result/*_training.jsonl`: Results from training phase
|
||||
- `./exp_result/*_test_no_memory.jsonl`: Test results without task memory
|
||||
- `./exp_result/*_test_with_memory.jsonl`: Test results with task memory
|
||||
- `./exp_result/frozenlake_summary.csv`: Statistical summary
|
||||
|
||||
### Task Memory Mechanism
|
||||
|
||||
The task memory system works as follows:
|
||||
|
||||
1. **Memory Creation**: During training, successful trajectories are sent to the ReMe service
|
||||
2. **Memory Retrieval**: During testing, the agent queries relevant memories based on the current map
|
||||
3. **Memory Application**: The agent uses retrieved memories to guide its decision-making
|
||||
|
||||
The experiment demonstrates how task memory can significantly improve performance, especially in challenging environments like the slippery FrozenLake.
|
||||
|
|
@ -13,7 +13,7 @@ from map_manager import MapManager
|
|||
|
||||
|
||||
def generate_training_configs(num_maps: int = 20, map_size: int = 4, is_slippery: bool=False) -> List[Dict]:
|
||||
"""Generate random maps for training/experience generation"""
|
||||
"""Generate random maps for training/task memory generation"""
|
||||
configs = []
|
||||
|
||||
for i in range(num_maps):
|
||||
|
|
@ -46,15 +46,15 @@ def generate_test_configs(num_test_maps: int = 100, is_slippery: bool = False) -
|
|||
map_desc = np.array([list(row) for row in map_data["map_desc"]], dtype='c')
|
||||
map_id = map_data["map_id"]
|
||||
|
||||
for use_exp in [True, False]:
|
||||
for use_memory in [True, False]:
|
||||
config = {
|
||||
"task_type": "test",
|
||||
"map_desc": map_desc,
|
||||
"map_size": 4,
|
||||
"is_slippery": is_slippery,
|
||||
"use_experience": use_exp,
|
||||
"use_task_memory": use_memory,
|
||||
"map_id": map_id,
|
||||
"task_id": f"test_map{map_id}_slip{is_slippery}_exp{use_exp}"
|
||||
"task_id": f"test_map{map_id}_slip{is_slippery}_mem{use_memory}"
|
||||
}
|
||||
configs.append(config)
|
||||
|
||||
|
|
@ -63,8 +63,8 @@ def generate_test_configs(num_test_maps: int = 100, is_slippery: bool = False) -
|
|||
|
||||
|
||||
def train(experiment_name: str, max_workers: int = 2, num_runs: int = 3, num_training_maps= 15, is_slippery: bool= False) -> None:
|
||||
"""Phase 1: Generate experience from random maps"""
|
||||
logger.info("🎯 Starting Training Phase - Generating Experience")
|
||||
"""Phase 1: Generate task memory from random maps"""
|
||||
logger.info("🎯 Starting Training Phase - Generating Task Memory")
|
||||
logger.info("=" * 60)
|
||||
|
||||
training_configs = generate_training_configs(num_maps=num_training_maps, map_size=4, is_slippery=is_slippery)
|
||||
|
|
@ -91,8 +91,8 @@ def train(experiment_name: str, max_workers: int = 2, num_runs: int = 3, num_tra
|
|||
task_configs=worker_configs,
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=False, # No experience in training phase
|
||||
make_experience=True,# Generate experience
|
||||
use_task_memory=False, # No task memory in training phase
|
||||
make_task_memory=True, # Generate task memory
|
||||
)
|
||||
future = agent.execute.remote()
|
||||
future_list.append(future)
|
||||
|
|
@ -115,8 +115,8 @@ def train(experiment_name: str, max_workers: int = 2, num_runs: int = 3, num_tra
|
|||
task_configs=training_configs,
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=False,
|
||||
make_experience=True
|
||||
use_task_memory=False,
|
||||
make_task_memory=True
|
||||
)
|
||||
results = agent.execute()
|
||||
dump_results()
|
||||
|
|
@ -131,7 +131,7 @@ def train(experiment_name: str, max_workers: int = 2, num_runs: int = 3, num_tra
|
|||
|
||||
|
||||
def test(experiment_name: str, max_workers: int = 2, num_runs: int = 5, num_test_maps: int = 100, is_slippery: bool=False) -> None:
|
||||
"""Phase 2: Test on fixed maps with/without experience"""
|
||||
"""Phase 2: Test on fixed maps with/without task memory"""
|
||||
logger.info("🧪 Starting Test Phase - Evaluating Performance")
|
||||
logger.info(f"📊 Testing on {num_test_maps} maps with {num_runs} runs each")
|
||||
logger.info("=" * 60)
|
||||
|
|
@ -140,12 +140,12 @@ def test(experiment_name: str, max_workers: int = 2, num_runs: int = 5, num_test
|
|||
path = Path("./exp_result")
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Group configs by experience usage for separate experiments
|
||||
exp_configs = [c for c in test_configs if c.get("use_experience", False)]
|
||||
no_exp_configs = [c for c in test_configs if not c.get("use_experience", False)]
|
||||
# Group configs by task memory usage for separate experiments
|
||||
memory_configs = [c for c in test_configs if c.get("use_task_memory", False)]
|
||||
no_memory_configs = [c for c in test_configs if not c.get("use_task_memory", False)]
|
||||
|
||||
logger.info(f"📝 Configs without experience: {len(no_exp_configs)}")
|
||||
logger.info(f"📝 Configs with experience: {len(exp_configs)}")
|
||||
logger.info(f"📝 Configs without task memory: {len(no_memory_configs)}")
|
||||
logger.info(f"📝 Configs with task memory: {len(memory_configs)}")
|
||||
|
||||
|
||||
|
||||
|
|
@ -156,37 +156,37 @@ def test(experiment_name: str, max_workers: int = 2, num_runs: int = 5, num_test
|
|||
f.write(json.dumps(result) + "\n")
|
||||
logger.info(f"💾 Test results saved to {output_file}")
|
||||
|
||||
# Test without experience first
|
||||
logger.info("🚫 Testing WITHOUT experience...")
|
||||
# Test without task memory first
|
||||
logger.info("🚫 Testing WITHOUT task memory...")
|
||||
all_results = []
|
||||
results_no_exp = run_test_configs(
|
||||
configs=no_exp_configs,
|
||||
results_no_memory = run_test_configs(
|
||||
configs=no_memory_configs,
|
||||
experiment_name=experiment_name,
|
||||
max_workers=max_workers,
|
||||
num_runs=num_runs,
|
||||
use_experience=False
|
||||
use_task_memory=False
|
||||
)
|
||||
all_results.extend(results_no_exp)
|
||||
dump_results("no_exp")
|
||||
all_results.extend(results_no_memory)
|
||||
dump_results("no_memory")
|
||||
|
||||
# Test with experience
|
||||
logger.info("✅ Testing WITH experience...")
|
||||
# Test with task memory
|
||||
logger.info("✅ Testing WITH task memory...")
|
||||
all_results = []
|
||||
results_with_exp = run_test_configs(
|
||||
configs=exp_configs,
|
||||
results_with_memory = run_test_configs(
|
||||
configs=memory_configs,
|
||||
experiment_name=experiment_name,
|
||||
max_workers=max_workers,
|
||||
num_runs=num_runs,
|
||||
use_experience=True
|
||||
use_task_memory=True
|
||||
)
|
||||
all_results.extend(results_with_exp)
|
||||
dump_results("with_exp")
|
||||
all_results.extend(results_with_memory)
|
||||
dump_results("with_memory")
|
||||
|
||||
return all_results
|
||||
|
||||
|
||||
def run_test_configs(configs: List[Dict], experiment_name: str, max_workers: int,
|
||||
num_runs: int, use_experience: bool) -> List[Dict]:
|
||||
num_runs: int, use_task_memory: bool) -> List[Dict]:
|
||||
"""Run a set of test configurations"""
|
||||
results = []
|
||||
|
||||
|
|
@ -200,8 +200,8 @@ def run_test_configs(configs: List[Dict], experiment_name: str, max_workers: int
|
|||
task_configs=worker_configs,
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=use_experience,
|
||||
make_experience=False
|
||||
use_task_memory=use_task_memory,
|
||||
make_task_memory=False
|
||||
)
|
||||
future = agent.execute.remote()
|
||||
future_list.append(future)
|
||||
|
|
@ -219,8 +219,8 @@ def run_test_configs(configs: List[Dict], experiment_name: str, max_workers: int
|
|||
task_configs=configs,
|
||||
experiment_name=experiment_name,
|
||||
num_runs=num_runs,
|
||||
use_experience=use_experience,
|
||||
make_experience=False
|
||||
use_task_memory=use_task_memory,
|
||||
make_task_memory=False
|
||||
)
|
||||
results = agent.execute()
|
||||
|
||||
|
|
@ -258,8 +258,8 @@ def main():
|
|||
is_slippery=is_slippery
|
||||
)
|
||||
|
||||
# Wait a bit for experience service to process
|
||||
logger.info("⏰ Waiting for experience service to process data...")
|
||||
# Wait a bit for task memory service to process
|
||||
logger.info("⏰ Waiting for task memory service to process data...")
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import json
|
||||
|
||||
import aiohttp
|
||||
|
||||
# API base URL
|
||||
|
|
@ -45,7 +46,9 @@ async def main():
|
|||
async with session.post(
|
||||
f"{base_url}/summary_personal_memory",
|
||||
json={
|
||||
"messages": messages,
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
],
|
||||
"workspace_id": workspace_id,
|
||||
},
|
||||
headers={"Content-Type": "application/json"}
|
||||
|
|
|
|||
287
cookbook/simple_demo/use_task_memory_mcp_demo.py
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Task Memory Demo for MemoryScope using MCP Client
|
||||
|
||||
This script demonstrates how to use the task memory capabilities of MemoryScope
|
||||
through the MCP client interface. It shows how to run an agent, summarize conversations,
|
||||
retrieve memories, and manage the memory workspace.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from fastmcp import Client
|
||||
from mcp.types import CallToolResult
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# API configuration
|
||||
MCP_URL = "http://0.0.0.0:8002/sse/"
|
||||
WORKSPACE_ID = "test_workspace"
|
||||
|
||||
|
||||
async def delete_workspace(client: Client) -> None:
|
||||
"""
|
||||
Delete the current workspace from the vector store
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "delete",
|
||||
}
|
||||
)
|
||||
print(f"Workspace '{WORKSPACE_ID}' deleted successfully")
|
||||
except Exception as e:
|
||||
print(f"Error deleting workspace: {e}")
|
||||
|
||||
|
||||
async def run_agent(client: Client, query: str, dump_messages: bool = False) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Run the agent with a specific query
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
query: The query to send to the agent
|
||||
dump_messages: Whether to save messages to a file
|
||||
|
||||
Returns:
|
||||
List of message objects from the conversation
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"react",
|
||||
arguments={"query": query}
|
||||
)
|
||||
|
||||
# Extract and display the answer
|
||||
response_data = json.loads(result.content)
|
||||
answer = response_data.get("answer", "")
|
||||
print(f"Agent response: {answer}")
|
||||
|
||||
# Get the conversation messages
|
||||
messages = response_data.get("messages", [])
|
||||
|
||||
# Optionally save messages to file
|
||||
if dump_messages and messages:
|
||||
with open("messages.jsonl", "w") as f:
|
||||
f.write(json.dumps(messages, indent=2, ensure_ascii=False))
|
||||
print(f"Messages saved to messages.jsonl")
|
||||
|
||||
return messages
|
||||
except Exception as e:
|
||||
print(f"Error running agent: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def run_summary(client: Client, messages: List[Dict[str, Any]], enable_dump_memory: bool = True) -> None:
|
||||
"""
|
||||
Generate a summary of conversation messages and create task memories
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
messages: List of message objects from a conversation
|
||||
enable_dump_memory: Whether to save memory list to a file
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not messages:
|
||||
print("No messages to summarize")
|
||||
return
|
||||
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"summary_task_memory",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads(result.content)
|
||||
|
||||
# Extract memory list from response
|
||||
memory_list = response_data.get("metadata", {}).get("memory_list", [])
|
||||
print(f"Memory list: {memory_list}")
|
||||
|
||||
# Optionally save memory list to file
|
||||
if enable_dump_memory and memory_list:
|
||||
with open("task_memory.jsonl", "w") as f:
|
||||
f.write(json.dumps(memory_list, indent=2, ensure_ascii=False))
|
||||
print(f"Memory saved to task_memory.jsonl")
|
||||
except Exception as e:
|
||||
print(f"Error running summary: {e}")
|
||||
|
||||
|
||||
async def run_retrieve(client: Client, query: str) -> str:
|
||||
"""
|
||||
Retrieve relevant task memories based on a query
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
query: The query to retrieve relevant memories
|
||||
|
||||
Returns:
|
||||
String containing the retrieved memory answer
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"retrieve_task_memory",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"query": query,
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads(result.content)
|
||||
|
||||
# Extract and return the answer
|
||||
answer = response_data.get("answer", "")
|
||||
print(f"Retrieved memory: {answer}")
|
||||
return answer
|
||||
except Exception as e:
|
||||
print(f"Error retrieving memory: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
async def run_agent_with_memory(client: Client, query_first: str, query_second: str, enable_dump_memory: bool = True) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Run the agent with memory augmentation
|
||||
|
||||
This function demonstrates how to use task memory to enhance agent responses:
|
||||
1. First run the agent with the second query to build memory
|
||||
2. Then summarize the conversation to create memories
|
||||
3. Retrieve relevant memories for the first query
|
||||
4. Run the agent with the first query augmented with retrieved memories
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
query_first: The query to run with memory augmentation
|
||||
query_second: The query to build initial memories
|
||||
enable_dump_memory: Whether to save memory list to a file
|
||||
|
||||
Returns:
|
||||
List of message objects from the final conversation
|
||||
"""
|
||||
# Run agent with second query to build initial memories
|
||||
print(f"\n--- Building memories with query: '{query_second}' ---")
|
||||
messages = await run_agent(client, query=query_second)
|
||||
|
||||
# Summarize conversation to create memories
|
||||
print("\n--- Summarizing conversation to create memories ---")
|
||||
await run_summary(client, messages, enable_dump_memory)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Retrieve relevant memories for the first query
|
||||
print(f"\n--- Retrieving memories for query: '{query_first}' ---")
|
||||
retrieved_memory = await run_retrieve(client, query_first)
|
||||
|
||||
# Run agent with first query augmented with retrieved memories
|
||||
print(f"\n--- Running agent with memory-augmented query ---")
|
||||
augmented_query = f"{retrieved_memory}\n\nUser Question:\n{query_first}"
|
||||
print(f"Augmented query: {augmented_query}")
|
||||
messages = await run_agent(client, query=augmented_query)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
async def dump_memory(client: Client, path: str = "./") -> None:
|
||||
"""
|
||||
Dump the vector store memories to disk
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
path: Directory path to save the memories
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "dump",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
print(f"Memory dumped to {path}")
|
||||
except Exception as e:
|
||||
print(f"Error dumping memory: {e}")
|
||||
|
||||
|
||||
async def load_memory(client: Client, path: str = "./") -> None:
|
||||
"""
|
||||
Load memories from disk into the vector store
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
path: Directory path to load the memories from
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "load",
|
||||
"path": path,
|
||||
}
|
||||
)
|
||||
print(f"Memory loaded from {path}")
|
||||
except Exception as e:
|
||||
print(f"Error loading memory: {e}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""
|
||||
Main function to demonstrate task memory workflow
|
||||
"""
|
||||
# Define example queries
|
||||
query1 = "Analyze Xiaomi Corporation"
|
||||
query2 = "Analyze the company Tesla."
|
||||
|
||||
print("=== Task Memory Demo (MCP Client) ===")
|
||||
|
||||
async with Client(MCP_URL) as client:
|
||||
# Step 1: Clean up workspace
|
||||
print("\n1. Deleting workspace...")
|
||||
await delete_workspace(client)
|
||||
|
||||
# Step 2: Run agent with first query and save messages
|
||||
print("\n2. Running agent with first query...")
|
||||
await run_agent(client, query=query1, dump_messages=True)
|
||||
|
||||
# Step 3: Demonstrate memory-augmented agent
|
||||
print("\n3. Running memory-augmented agent workflow...")
|
||||
await run_agent_with_memory(client, query_first=query1, query_second=query2)
|
||||
|
||||
# Step 4: Demonstrate memory persistence
|
||||
print("\n4. Dumping memory to disk...")
|
||||
await dump_memory(client)
|
||||
|
||||
print("\n5. Loading memory from disk...")
|
||||
await load_memory(client)
|
||||
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
661
doc/README.md
|
|
@ -1,661 +0,0 @@
|
|||
# ExperienceMaker
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/figure/logo.jpg" alt="ExperienceMaker Logo" width="100%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://pypi.org/project/experiencemaker/"><img src="https://img.shields.io/badge/python-3.12+-blue" alt="Python Version"></a>
|
||||
<a href="https://pypi.org/project/experiencemaker/"><img src="https://img.shields.io/badge/pypi-v0.1.1-blue?logo=pypi" alt="PyPI Version"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="https://github.com/modelscope/ExperienceMaker"><img src="https://img.shields.io/github/stars/modelscope/ExperienceMaker?style=social" alt="GitHub Stars"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>A comprehensive framework to make & reuse & share experience for AI agent</strong><br>
|
||||
<em>Empowering agents to learn from the past and excel in the future</em>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 📰 What's New
|
||||
- **[2025-08]** 🚀 MCP is now available! → [Quick Start Guide](./doc/mcp_quick_start.md)
|
||||
- **[2025-07]** 🎉 ExperienceMaker v0.1.1 is now available on [PyPI](https://pypi.org/project/experiencemaker/)!
|
||||
- **[2025-07]** 📚 Complete documentation and quick start guides released
|
||||
- **[2025-06]** 🚀 Multi-backend vector store support (Elasticsearch & ChromaDB)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 What's Next
|
||||
- **Pre-built Experience Libraries**: Domain repositories (Finance/Coding/Education/Research) + community marketplace
|
||||
- **Rich Experience Formats**: Executable code/tool configs/pipeline templates/workflows
|
||||
- **Experience Validation**: Quality analysis + cross-task effectiveness + auto-refinement
|
||||
- **Universal Trajectory Extraction**: Raw logs/multimodal data/execution traces → experiences
|
||||
|
||||
---
|
||||
|
||||
## 🌟 What is ExperienceMaker?
|
||||
ExperienceMaker is a framework that transforms how AI agents learn and improve through **experience-driven intelligence**.
|
||||
By automatically extracting, storing, and intelligently reusing experiences from agent trajectories, it enables continuous learning and progressive skill enhancement.
|
||||
|
||||
### ✨ Core Capabilities
|
||||
|
||||
#### 🔍 **Intelligent Experience Summarizer**
|
||||
- **Success Pattern Recognition**: Identify what works and understand the underlying principles
|
||||
- **Failure Analysis**: Learn from mistakes to avoid repeating them in future tasks
|
||||
- **Comparative Insights**: Understand the critical differences between successful and failed approaches
|
||||
- **Multistep Trajectory Processing**: Break down complex tasks into learnable, actionable segments
|
||||
|
||||
#### 🎯 **Smart Experience Retriever**
|
||||
- **Semantic Search**: Find relevant experiences using advanced embedding models and semantic understanding
|
||||
- **Context-Aware Ranking**: Prioritize the most applicable experiences for current task contexts
|
||||
- **Dynamic Rewriting**: Intelligently adapt experiences to fit new situations and requirements
|
||||
- **Multi-modal Support**: Handle various input types including query, messages
|
||||
|
||||
#### 🗄️ **Scalable Experience Management**
|
||||
- **Multiple Storage Backends**: Choose from Elasticsearch (production-ready), ChromaDB (development), or file-based storage (testing)
|
||||
- **Workspace Isolation**: Organize experiences by projects, domains, or teams with complete separation
|
||||
- **Deduplication & Validation**: Ensure high-quality, unique experience storage with automated quality control
|
||||
- **Batch Operations**: Efficiently handle large-scale experience processing with optimized performance
|
||||
|
||||
#### 🔧 **Developer-Friendly Architecture**
|
||||
- **REST API Interface**: Seamless integration with existing systems through clean API design
|
||||
- **Modular Pipeline Design**: Compose custom workflows from atomic operations with maximum flexibility
|
||||
- **Flexible Configuration**: YAML files and command-line overrides for easy customization
|
||||
- **Experience Store**: Ready-to-use out of the box — there’s no need for you to manually summarize experiences. You can directly leverage existing, comprehensive experience datasets to greatly enhance your agent’s capabilities.
|
||||
<p align="center">
|
||||
<img src="doc/figure/framework.png" alt="ExperienceMaker Architecture" width="70%">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
### Option 1: Install from PyPI (Recommended)
|
||||
|
||||
```bash
|
||||
pip install experiencemaker
|
||||
```
|
||||
|
||||
### Option 2: Install from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ExperienceMaker.git
|
||||
cd ExperienceMaker
|
||||
pip install .
|
||||
```
|
||||
|
||||
## ⚙️ Environment Setup
|
||||
|
||||
Create a `.env` file in your project root directory:
|
||||
|
||||
```bash
|
||||
# Required: LLM API configuration
|
||||
LLM_API_KEY="sk-xxx"
|
||||
LLM_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# Required: Embedding model configuration
|
||||
EMBEDDING_MODEL_API_KEY="sk-xxx"
|
||||
EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# Optional: Elasticsearch configuration (if using Elasticsearch backend)
|
||||
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 🌐 HTTP Service
|
||||
|
||||
For testing and development, use the `local_file` backend:
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
```
|
||||
|
||||
💡 **Pro Tip**: Check out our [Configuration Guide](./doc/configuration_guide.md) for detailed configuration topics
|
||||
including custom pipelines, operation parameters, and advanced configuration methods.
|
||||
|
||||
The service will start on `http://localhost:8001`
|
||||
|
||||
### 🔌 MCP Server
|
||||
|
||||
ExperienceMaker now supports Model Context Protocol (MCP) for seamless integration with MCP-compatible clients like Claude Desktop:
|
||||
|
||||
```bash
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=stdio \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
```
|
||||
|
||||
For SSE transport (Server-Sent Events):
|
||||
```bash
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=sse \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
```
|
||||
|
||||
🔗 **For detailed MCP setup and usage examples**, see our [MCP Quick Start Guide](./doc/mcp_quick_start.md).
|
||||
|
||||
### 🔍 Production Setup with Elasticsearch Backend
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=elasticsearch
|
||||
```
|
||||
|
||||
**Setup Elasticsearch:**
|
||||
```bash
|
||||
export ES_HOSTS="http://localhost:9200"
|
||||
# Quick setup using Elastic's official script
|
||||
curl -fsSL https://elastic.co/start-local | sh
|
||||
```
|
||||
📖 **Need Help?** Refer to [Vector Store Setup](./doc/vector_store_setup.md) for comprehensive deployment guidance.
|
||||
|
||||
## 📝 Your First ExperienceMaker Script
|
||||
|
||||
Here's how to get started!
|
||||
Note the `workspace_id` serves as your experience storage namespace. Experiences in different workspaces remain completely isolated and cannot access each other.
|
||||
|
||||
### 📊 Call Summarizer Examples
|
||||
|
||||
Transform conversation trajectories into valuable experiences using batch summarization. Each trajectory contains:
|
||||
|
||||
- **Message**: Complete conversation history between user and agent
|
||||
- **Score**: Performance rating (0-1 scale, where 0=failure, 1=success)
|
||||
|
||||
The summarizer analyzes these trajectories to extract actionable insights and patterns for future interactions.
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/summarizer", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"traj_list": [
|
||||
{"messages": [{"role": "user", "content": "hello world"}], "score": 1.0}
|
||||
]
|
||||
})
|
||||
|
||||
experience_list = response.json()["experience_list"]
|
||||
for experience in experience_list:
|
||||
print(experience)
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/summarizer" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"traj_list": [
|
||||
{
|
||||
"messages": [{"role": "user", "content": "hello world"}],
|
||||
"score": 1.0
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function callSummarizer() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/summarizer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
traj_list: [
|
||||
{
|
||||
messages: [{ role: "user", content: "hello world" }],
|
||||
score: 1.0
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const experienceList = data.experience_list;
|
||||
|
||||
experienceList.forEach(experience => {
|
||||
console.log(experience);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
callSummarizer();
|
||||
```
|
||||
</details>
|
||||
|
||||
### 🔍 Call Retriever Examples
|
||||
|
||||
Intelligently search and retrieve the most relevant experiences from your workspace to enhance decision-making. The retriever:
|
||||
|
||||
- **Finds** the top-k most similar experiences based on semantic similarity to your query
|
||||
- **Returns** pre-assembled context ready for immediate use, or raw experience data for custom processing
|
||||
- **Leverages** your workspace's accumulated knowledge to provide contextually relevant insights
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/retriever", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"query": "what is the meaning of life?",
|
||||
"top_k": 1,
|
||||
})
|
||||
|
||||
experience_merged: str = response.json()["experience_merged"]
|
||||
print(f"experience_merged={experience_merged}")
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/retriever" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"query": "what is the meaning of life?",
|
||||
"top_k": 1
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function callRetriever() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/retriever', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
query: "what is the meaning of life?",
|
||||
top_k: 1
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const experienceMerged = data.experience_merged;
|
||||
|
||||
console.log(`experience_merged=${experienceMerged}`);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
callRetriever();
|
||||
```
|
||||
</details>
|
||||
|
||||
### 💾 Dump Experiences From Vector Store
|
||||
|
||||
Export and backup your valuable experience data for archival, analysis, or migration purposes. This operation:
|
||||
|
||||
- **Extracts** all experiences from the specified workspace in the vector store
|
||||
- **Saves** them to a structured JSONL file at `{path}/{workspace_id}.jsonl`
|
||||
- **Preserves** complete experience metadata and embeddings for future restoration
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "dump",
|
||||
"path": "./",
|
||||
})
|
||||
print(response.json())
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "dump",
|
||||
"path": "./"
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function dumpExperiences() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "dump",
|
||||
path: "./"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
dumpExperiences();
|
||||
```
|
||||
</details>
|
||||
|
||||
### 📥 Load Experiences To Vector Store
|
||||
|
||||
Import and restore previously exported experience data to populate your workspace with existing knowledge. This operation:
|
||||
|
||||
- **Reads** experience data from the JSONL file located at `{path}/{workspace_id}.jsonl`
|
||||
- **Reconstructs** the vector embeddings and indexes them in the specified workspace
|
||||
- **Enables** immediate access to imported experiences for retrieval and decision-making
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "load",
|
||||
"path": "./",
|
||||
})
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "load",
|
||||
"path": "./"
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function loadExperiences() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "load",
|
||||
path: "./"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
loadExperiences();
|
||||
```
|
||||
</details>
|
||||
|
||||
💡 **Need More Advanced Operations?** For additional workspace management features(e.g. delete_workspace,
|
||||
copy_workspace), advanced configuration options, and troubleshooting guidance, check out our
|
||||
comprehensive [Quick Start Guide](./cookbook/simple_demo/quick_start.md).
|
||||
|
||||
🎭 **Want to See It in Action?** We've prepared a [simple react agent](./cookbook/simple_demo/simple_demo.py) that demonstrates how to enhance agent capabilities by integrating summarizer and retriever components, achieving significantly better performance.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Experiments
|
||||
|
||||
### 🌍 Experiment on Appworld
|
||||
|
||||
We test ExperienceMaker on Appworld with qwen3-8b:
|
||||
|
||||
| Method | pass@1 | pass@2 | pass@4 |
|
||||
|--------------------------------|-----------|-------------|-----------|
|
||||
| w/o ExperienceMaker (baseline) | 0.083 | 0.140 | 0.228 |
|
||||
| **w ExperienceMaker** | | | |
|
||||
|experience(Direct Use) | **0.109** | **0.175** | **0.281** |
|
||||
|
||||
Pass@K measures the probability that at least one out of K generated samples successfully completes the task (achieves score=1).
|
||||
The current experiments use an internal AppWorld environment which may have slight discrepancies, and we will soon update with experimental results from the standard AppWorld environment.
|
||||
|
||||
You may find more details to reproduce this experiment in [quickstart.md](cookbook/appworld/quickstart.md)
|
||||
|
||||
|
||||
### 🧊 Experiment on Frozenlake
|
||||
|
||||
| without experience | with experience |
|
||||
|:-------------------------------------------------------------------------------------------:|:-------------------------------------------:|
|
||||
| <p align="center"><img src="doc/figure/frozenlake_failure.gif" alt="GIF 1" width="30%"></p> | <p align="center"><img src="doc/figure/frozenlake_success.gif" alt="GIF 2" width="30%"></p>
|
||||
|
||||
We test on 100 random frozenlake map with qwen3-8b:
|
||||
|
||||
| Method | pass rate |
|
||||
|-------------------------------|------------------|
|
||||
| w/o ExperienceMaker (baseline) | 0.66 |
|
||||
| **w ExperienceMaker** | |
|
||||
| [1] experience(Direct Use) | 0.72 **(+9.1%)** |
|
||||
| [2] experience(LLM Rewritten) | 0.72 **(+9.1%)** |
|
||||
|
||||
We also noticed that in such simple scenarios, not using LLM rewriting may actually yield better results.
|
||||
|
||||
Therefore, in some simple scenarios, you can also try disabling LLM rewriting by simply changing the following in default_config.yaml:
|
||||
|
||||
```yaml
|
||||
rewrite_experience_op:
|
||||
params:
|
||||
enable_llm_rewrite: false # change this to false
|
||||
```
|
||||
|
||||
You may find more details to reproduce this experiment in [quickstart.md](cookbook/frozenlake/quickstart.md)
|
||||
|
||||
### 🔧 Experiment on BFCL-V3
|
||||
|
||||
Coming Soon! Stay tuned for comprehensive evaluation results.
|
||||
|
||||
---
|
||||
|
||||
## 🏪 Ready-made Experience Store
|
||||
|
||||
ExperienceMaker provides pre-built experience libraries to jumpstart your agent's capabilities.
|
||||
You can directly load these curated experiences into your workspace and start benefiting from accumulated knowledge
|
||||
immediately.
|
||||
|
||||
### 📦 Available Experience Libraries
|
||||
|
||||
- **`appworld_v1.jsonl`**: Comprehensive experiences from Appworld agent interactions, covering complex task planning
|
||||
and execution patterns
|
||||
- **`bfcl_v1.jsonl`**: Function calling experiences from Berkeley Function-Calling Leaderboard tasks
|
||||
|
||||
### 🚀 Quick Start with Pre-built Experiences
|
||||
|
||||
Here's how to load and use the Appworld experience library:
|
||||
|
||||
#### Step 1: Load Pre-built Experiences
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Load Appworld experiences into your workspace
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"action": "load",
|
||||
"path": "./library/",
|
||||
})
|
||||
|
||||
print(f"loading result result={response.json()}")
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "appworld_v1",
|
||||
"action": "load",
|
||||
"path": "./library/"
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Step 2: Retrieve Relevant Experiences
|
||||
|
||||
Now you can query the loaded experiences to get contextual guidance for your tasks:
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Query for app interaction experiences
|
||||
response = requests.post(url="http://0.0.0.0:8001/retriever", json={
|
||||
"workspace_id": "appworld_v1",
|
||||
"query": "How to navigate to settings and update user profile information?",
|
||||
"top_k": 1,
|
||||
})
|
||||
|
||||
experience_merged = response.json()["experience_merged"]
|
||||
print(f"Retrieved experiences: {experience_merged}")
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/retriever" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "appworld_v1",
|
||||
"query": "How to navigate to settings and update user profile information?",
|
||||
"top_k": 1
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- **[Quick Start](./cookbook/simple_demo/quick_start.md)**: This guide will help you get started with ExperienceMaker quickly using practical examples.
|
||||
- **[Vector Store Setup](./doc/vector_store_setup.md)**: Complete production deployment guide
|
||||
- **[Configuration Guide](./doc/configuration_guide.md)**: Describes all available command-line parameters for ExperienceMaker Service
|
||||
- **[Operations Documentation](./doc/operations_documentation.md)**: Comprehensive operations configuration reference
|
||||
- **[Example Collection](./cookbook)**: Practical examples and use cases
|
||||
- **[Future RoadMap](./doc/future_roadmap.md)**: Our vision and upcoming features
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
We warmly welcome contributions from the community! Here's how you can help make ExperienceMaker even better:
|
||||
|
||||
### 🐛 **Report Issues**
|
||||
- Bug reports with detailed reproduction steps
|
||||
- Feature requests and enhancement suggestions
|
||||
- Documentation improvements and clarifications
|
||||
- Performance optimization ideas
|
||||
|
||||
### 💻 **Code Contributions**
|
||||
- New operations and tools development
|
||||
- Backend implementations and optimizations
|
||||
- API enhancements and new endpoints
|
||||
- Test coverage improvements and quality assurance
|
||||
|
||||
### 📝 **Documentation**
|
||||
- Usage examples and comprehensive tutorials
|
||||
- Best practices guides and design patterns
|
||||
- Translation and localization efforts
|
||||
|
||||
---
|
||||
## 📄 Citation
|
||||
If you use ExperienceMaker in your research or projects, please cite:
|
||||
```bibtex
|
||||
@software{ExperienceMaker,
|
||||
title = {ExperienceMaker: A Comprehensive Framework for AI Agent Experience Generation and Reuse},
|
||||
author = {The ExperienceMaker Team},
|
||||
url = {https://github.com/modelscope/ExperienceMaker},
|
||||
month = {08},
|
||||
year = {2025},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
## ⚖️ License
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](./LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
1. library 转化 @zouyin
|
||||
2. index.html @jinli
|
||||
3. reme_ai两个personal的调通
|
||||
4. doc
|
||||
1. readme @jiaji
|
||||
2. experience maker @jiaji
|
||||
3. personal @jinli
|
||||
5. 新增op @zouyin
|
||||
6. cookbook
|
||||
1. appworld @jiaji P2
|
||||
2. bfcl @zouyin P1
|
||||
3. frozenlake @jiaji
|
||||
4. simple_demo @jinli
|
||||
|
|
@ -1,337 +0,0 @@
|
|||
# Configuration Guide
|
||||
|
||||
This document describes all available parameters for ExperienceMaker Service.
|
||||
The application uses [OmegaConf](https://omegaconf.readthedocs.io/) for configuration management, supporting both YAML
|
||||
files and command-line overrides.
|
||||
|
||||
## Configuration Loading Priority
|
||||
|
||||
1. Default values from `AppConfig` dataclass
|
||||
2. Pre-defined YAML configuration file (default: `demo_config.yaml`)
|
||||
3. Custom YAML file (if `config_path` is specified)
|
||||
4. Command-line overrides
|
||||
|
||||
## 🏗️ Configuration Architecture
|
||||
|
||||
ExperienceMaker uses a layered configuration system with the following priority order:
|
||||
|
||||
1. **Default Configuration** (lowest priority)
|
||||
2. **YAML Configuration File**
|
||||
3. **Command Line Arguments** (highest priority)
|
||||
|
||||
## Basic Bash Usage
|
||||
|
||||
```bash
|
||||
experiencemaker [parameter1=value1] [parameter2=value2] ...
|
||||
```
|
||||
|
||||
## 🧩 YAML Configuration Composition
|
||||
|
||||
The YAML configuration file follows a specific composition pattern that enables flexible and modular configuration:
|
||||
|
||||
### 1. Resource Declaration
|
||||
|
||||
First, you declare the three core resources that form the foundation of the system:
|
||||
|
||||
- **`llm`**: Language model configurations
|
||||
- **`embedding_model`**: Embedding model configurations
|
||||
- **`vector_store`**: Vector storage configurations
|
||||
|
||||
In these sections, `default` (or any custom name) represents a declared configuration object that can be referenced
|
||||
later:
|
||||
|
||||
```yaml
|
||||
llm:
|
||||
default: # This is a declared LLM configuration object
|
||||
backend: openai_compatible
|
||||
model_name: qwen3-32b
|
||||
|
||||
embedding_model:
|
||||
default: # This is a declared embedding model configuration object
|
||||
backend: openai_compatible
|
||||
model_name: text-embedding-v4
|
||||
|
||||
vector_store:
|
||||
default: # This is a declared vector store configuration object
|
||||
backend: local_file
|
||||
embedding_model: default
|
||||
```
|
||||
|
||||
### 2. Operation Backend Registration
|
||||
|
||||
In the `op` section, each operation declares its `backend` implementation. The backend names are registered through
|
||||
`@OP_REGISTRY.register()` decorator, typically converting camel-case class names to underscore format:
|
||||
|
||||
```yaml
|
||||
op:
|
||||
recall_experience_op:
|
||||
backend: recall_experience_op # Registered via @OP_REGISTRY.register()
|
||||
```
|
||||
|
||||
### 3. Resource References
|
||||
|
||||
Operations reference the previously declared resources using their names:
|
||||
|
||||
```yaml
|
||||
op:
|
||||
recall_experience_op:
|
||||
backend: recall_experience_op
|
||||
llm: default # References the declared LLM object
|
||||
embedding_model: default # References the declared embedding model object
|
||||
vector_store: default # References the declared vector store object
|
||||
```
|
||||
|
||||
### 4. Pipeline
|
||||
|
||||
Pipeline configurations use a special syntax to define operation flows:
|
||||
|
||||
- `->`: Sequential execution
|
||||
- `[]`: Parallel execution group
|
||||
- `|`: Alternative operations within parallel group
|
||||
|
||||
### Examples
|
||||
|
||||
```yaml
|
||||
# Sequential pipeline
|
||||
api:
|
||||
retriever: op1->op2->op3
|
||||
|
||||
# Parallel execution
|
||||
summarizer: op1->[op2|op3|op4]->op5
|
||||
|
||||
# Complex pipeline with nested parallel operations
|
||||
vector_store: preprocess_op->[recall_op->rerank_op|backup_op]->merge_op
|
||||
```
|
||||
|
||||
This compositional approach enables:
|
||||
|
||||
- **Modularity**: Declare resources once, reference everywhere
|
||||
- **Flexibility**: Mix and match different backends and configurations
|
||||
- **Complexity**: Build sophisticated processing chains through pipeline syntax
|
||||
|
||||
## 📁 Configuration Structure
|
||||
|
||||
```yaml
|
||||
# Service Configuration
|
||||
http_service:
|
||||
host: "0.0.0.0"
|
||||
port: 8001
|
||||
timeout_keep_alive: 600
|
||||
limit_concurrency: 64
|
||||
|
||||
# Pipeline Definitions
|
||||
api:
|
||||
retriever: recall_experience_op->rerank_experience_op->rewrite_experience_op
|
||||
summarizer: trajectory_preprocess_op->[success_extraction_op|failure_extraction_op]->experience_validation_op
|
||||
vector_store: vector_store_action_op
|
||||
|
||||
# Operation Configurations
|
||||
op:
|
||||
operation_name:
|
||||
backend: operation_name # Register through `@OP_REGISTRY.register()`, typically by converting camel-cased types into underscored names
|
||||
llm: default # Optional: reference to LLM config, Register through `@LLM_REGISTRY.register()`
|
||||
embedding_model: default # Optional: reference to embedding config, Register through `@EMBEDDING_MODEL_REGISTRY.register()`
|
||||
vector_store: default # Optional: reference to vector store config, Register through `@VECTOR_STORE_REGISTRY.register()`
|
||||
params: # Operation-specific parameters
|
||||
param1: value1
|
||||
param2: value2
|
||||
|
||||
# Resource Configurations
|
||||
llm:
|
||||
default:
|
||||
backend: openai_compatible
|
||||
model_name: qwen3-32b
|
||||
params:
|
||||
temperature: 0.6
|
||||
|
||||
embedding_model:
|
||||
default:
|
||||
backend: openai_compatible
|
||||
model_name: text-embedding-v4
|
||||
params:
|
||||
dimensions: 1024
|
||||
|
||||
vector_store:
|
||||
default:
|
||||
backend: local_file
|
||||
embedding_model: default
|
||||
```
|
||||
|
||||
## Detailed Configuration Parameters
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|----------------------|--------|-----------------|----------------------------------------------------------------------|-------------------------------------------|
|
||||
| `pre_defined_config` | string | `"demo_config"` | Name of the pre-defined configuration file (without .yaml extension) | `pre_defined_config=full_pipeline_config` |
|
||||
| `config_path` | string | `""` | Path to custom configuration YAML file | `config_path=/path/to/config.yaml` |
|
||||
|
||||
## HTTP Service Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|-----------------------------------|---------|---------------|-----------------------------------|---------------------------------------|
|
||||
| `http_service.host` | string | `"0.0.0.0"` | Host address for the HTTP service | `http_service.host=127.0.0.1` |
|
||||
| `http_service.port` | integer | `8001` | Port number for the HTTP service | `http_service.port=8080` |
|
||||
| `http_service.timeout_keep_alive` | integer | `600` | Keep-alive timeout in seconds | `http_service.timeout_keep_alive=600` |
|
||||
| `http_service.limit_concurrency` | integer | `64` | Maximum concurrent connections | `http_service.limit_concurrency=128` |
|
||||
|
||||
## Thread Pool Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|---------------------------|---------|---------------|----------------------------------|------------------------------|
|
||||
| `thread_pool.max_workers` | integer | `10` | Maximum number of worker threads | `thread_pool.max_workers=20` |
|
||||
|
||||
## API Pipeline Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|--------------------|--------|---------------|------------------------------------------|--------------------------------------------------------------|
|
||||
| `api.retriever` | string | `""` | Pipeline definition for retriever API | `api.retriever="build_query_op->recall_vector_store_op"` |
|
||||
| `api.summarizer` | string | `""` | Pipeline definition for summarizer API | `api.summarizer="simple_summary_op->update_vector_store_op"` |
|
||||
| `api.vector_store` | string | `""` | Pipeline definition for vector store API | `api.vector_store="vector_store_action_op"` |
|
||||
|
||||
## Operation Configuration
|
||||
|
||||
Operations are configured using the pattern `op.{operation_name}.{parameter}`. Each operation can have the following
|
||||
parameters:
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|------------------------------|--------|---------------|--------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `op.{name}.backend` | string | `""` | Backend implementation class name | `op.build_query_op.backend=build_query_op` |
|
||||
| `op.{name}.prompt_file_path` | string | `""` | Path to prompt template file | `op.react_op.prompt_file_path=/path/to/prompt.yaml` |
|
||||
| `op.{name}.prompt_dict` | dict | `{}` | Direct prompt configuration dictionary | `op.react_op.prompt_dict.system="You are an AI assistant"` |
|
||||
| `op.{name}.llm` | string | `""` | Reference to LLM configuration | `op.react_op.llm=default` |
|
||||
| `op.{name}.embedding_model` | string | `""` | Reference to embedding model configuration | `op.recall_op.embedding_model=default` |
|
||||
| `op.{name}.vector_store` | string | `""` | Reference to vector store configuration | `op.recall_op.vector_store=default` |
|
||||
| `op.{name}.params.{param}` | any | `{}` | Operation-specific parameters | The parameter reference is in [operations_documentation.md](operations_documentation.md) |
|
||||
|
||||
## LLM Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|-----------------------------|--------|---------------|----------------------------|-----------------------------------------|
|
||||
| `llm.{name}.backend` | string | `""` | LLM backend implementation | `llm.default.backend=openai_compatible` |
|
||||
| `llm.{name}.model_name` | string | `""` | Model name identifier | `llm.default.model_name=qwen3-32b` |
|
||||
| `llm.{name}.params.{param}` | any | `{}` | LLM-specific parameters | `llm.default.params.temperature=0.6` |
|
||||
|
||||
## Embedding Model Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|-----------------------------------------|--------|---------------|----------------------------------------|--------------------------------------------------------|
|
||||
| `embedding_model.{name}.backend` | string | `""` | Embedding model backend implementation | `embedding_model.default.backend=openai_compatible` |
|
||||
| `embedding_model.{name}.model_name` | string | `""` | Embedding model name identifier | `embedding_model.default.model_name=text-embedding-v4` |
|
||||
| `embedding_model.{name}.params.{param}` | any | `{}` | Model-specific parameters | `embedding_model.default.params.dimensions=1024` |
|
||||
|
||||
## Vector Store Configuration
|
||||
|
||||
| Parameter | Type | Default Value | Description | Example |
|
||||
|---------------------------------------|--------|---------------|--------------------------------------------|-----------------------------------------------------------|
|
||||
| `vector_store.{name}.backend` | string | `""` | Vector store backend implementation | `vector_store.default.backend=elasticsearch` |
|
||||
| `vector_store.{name}.embedding_model` | string | `""` | Reference to embedding model configuration | `vector_store.default.embedding_model=default` |
|
||||
| `vector_store.{name}.params.{param}` | any | `{}` | Vector store-specific parameters | `vector_store.default.params.store_dir=file_vector_store` |
|
||||
|
||||
|
||||
## 🎯 Practical Examples
|
||||
|
||||
### Example 1
|
||||
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8002 \
|
||||
thread_pool.max_workers=64 \
|
||||
op.recall_experience_op.params.retrieve_top_k=50 \
|
||||
op.rerank_experience_op.params.top_k=10 \
|
||||
llm.default.params.temperature=0.1
|
||||
```
|
||||
|
||||
### Example 2
|
||||
|
||||
```yaml
|
||||
# dev_config.yaml
|
||||
http_service:
|
||||
port: 8003
|
||||
|
||||
api:
|
||||
retriever: recall_experience_op->rerank_experience_op
|
||||
|
||||
op:
|
||||
recall_experience_op:
|
||||
params:
|
||||
retrieve_top_k: 5 # Faster for development
|
||||
|
||||
rerank_experience_op:
|
||||
params:
|
||||
top_k: 3
|
||||
|
||||
llm:
|
||||
default:
|
||||
model_name: qwen-turbo
|
||||
params:
|
||||
temperature: 0.8
|
||||
```
|
||||
|
||||
```bash
|
||||
experiencemaker config_path=dev_config.yaml
|
||||
```
|
||||
|
||||
### Example 3: Multi-Backend Setup
|
||||
|
||||
```yaml
|
||||
# multi_backend_config.yaml
|
||||
llm:
|
||||
fast:
|
||||
backend: openai_compatible
|
||||
model_name: qwen-turbo
|
||||
params:
|
||||
temperature: 0.9
|
||||
|
||||
accurate:
|
||||
backend: openai_compatible
|
||||
model_name: gpt-4
|
||||
params:
|
||||
temperature: 0.1
|
||||
|
||||
op:
|
||||
quick_extraction_op:
|
||||
backend: success_extraction_op
|
||||
llm: fast
|
||||
|
||||
detailed_validation_op:
|
||||
backend: experience_validation_op
|
||||
llm: accurate
|
||||
params:
|
||||
validation_threshold: 0.8
|
||||
```
|
||||
|
||||
## 📋 Configuration Tips
|
||||
|
||||
1. **Start Simple**: Begin with the default configuration and override specific parameters
|
||||
2. **Use Environment Variables**: Set API keys and URLs in `.env` file
|
||||
3. **Parameter Validation**: Invalid parameters will cause startup errors with detailed messages
|
||||
4. **Performance Tuning**: Adjust `retrieve_top_k`, `top_k`, and `max_workers` based on your needs
|
||||
5. **Pipeline Testing**: Use simple pipelines first, then gradually add complexity
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Configuration Not Loading:**
|
||||
|
||||
```bash
|
||||
# Check if config file exists and has correct YAML syntax
|
||||
experiencemaker config_path=/full/path/to/config.yaml
|
||||
```
|
||||
|
||||
**Parameter Override Not Working:**
|
||||
|
||||
```bash
|
||||
# Use exact parameter path from configuration structure
|
||||
experiencemaker op.operation_name.params.parameter_name=value
|
||||
```
|
||||
|
||||
**Pipeline Syntax Errors:**
|
||||
|
||||
- Check for balanced brackets `[]`
|
||||
- Ensure operation names exist in `op` section
|
||||
- Use `|` only within `[]` groups
|
||||
|
||||
---
|
||||
|
||||
🎯 **Advanced Configuration Mastery!** You can now create sophisticated ExperienceMaker setups tailored to your specific
|
||||
needs.
|
||||
|
Before Width: | Height: | Size: 417 KiB |
|
Before Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 1,009 KiB |
|
Before Width: | Height: | Size: 508 KiB |
BIN
doc/figure/reme_logo.jpg
Normal file
|
After Width: | Height: | Size: 458 KiB |
|
|
@ -1,59 +0,0 @@
|
|||
# 🗺️ ExperienceMaker Future Roadmap
|
||||
|
||||
## P0 - Ready-to-Use Experience Libraries
|
||||
|
||||
We aim to build curated experience libraries for complex scenarios, providing battle-tested best practices and lessons learned rather than simple documentation aggregation.
|
||||
|
||||
Just as financial analysts develop analytical frameworks, senior engineers establish coding standards, and education experts create teaching methodologies, AI agents can build professional experience repositories. Start your AI projects standing on the shoulders of giants.
|
||||
|
||||
**Core Features:**
|
||||
|
||||
- [ ] Pre-built experience libraries for key domains
|
||||
- [ ] Finance
|
||||
- [ ] Coding
|
||||
- [ ] Education
|
||||
- [ ] Research
|
||||
- [ ] Experience marketplace: community-driven experience sharing and exchange
|
||||
|
||||
## P0 - Support for Rich Experience Formats
|
||||
|
||||
Expert knowledge extends beyond text to include debugged code, fine-tuned toolchains, and validated workflows. We aim to integrate diverse experience carriers:
|
||||
|
||||
- [ ] **Executable Code**: Functions, code files, and scripts
|
||||
- [ ] **Tool Integration**: APIs, MCP configurations, and tool setups
|
||||
- [ ] **Pipeline Templates**: Agent execution pipelines and multi-step tool combinations
|
||||
|
||||
## P0 - MCP Integration
|
||||
|
||||
Modernize our API architecture by migrating three core APIs to the Model Context Protocol (MCP) standard for improved interoperability and standardization.
|
||||
|
||||
- [ ] Summarizer API
|
||||
- [ ] Retriever API
|
||||
- [ ] Vector Store API
|
||||
|
||||
## P1 - Experience Validation & Optimization
|
||||
|
||||
AI-powered analysis of experience usage patterns and effectiveness, with automatic quality optimization and cross-task validation feedback loops.
|
||||
|
||||
## P2 - Universal Trajectory Experience Extraction
|
||||
|
||||
### Raw Data Processing
|
||||
- [ ] Automatic extraction of valuable experiences from agent execution logs
|
||||
- [ ] Multimodal support: images, videos, and other formats
|
||||
|
||||
### Vision
|
||||
Transform valuable experience data from daily work into usable insights:
|
||||
- Communication techniques from emails
|
||||
- Optimization insights from code commits
|
||||
- Decision-making processes from meeting recordings
|
||||
|
||||
Enable AI to naturally become stronger through everyday work, rather than wasting real-world experience data due to format limitations.
|
||||
|
||||
## P2 - Open Source Experience Libraries
|
||||
|
||||
Democratize AI experience sharing by making curated experience libraries publicly available on Hugging Face, enabling the broader AI community to benefit from and contribute to professional experience repositories.
|
||||
|
||||
- [ ] **Hugging Face Integration**: Upload and maintain experience libraries on Hugging Face Hub
|
||||
- [ ] **Community Contributions**: Enable community-driven experience library improvements and additions
|
||||
- [ ] **Standardized Formats**: Establish standard formats for experience sharing across different domains
|
||||
- [ ] **Version Control**: Implement versioning system for experience library updates and improvements
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
# ExperienceMaker MCP Quick Start Guide
|
||||
# MCP Quick Start Guide
|
||||
|
||||
This guide will help you get started with ExperienceMaker using the Model Context Protocol (MCP) interface for seamless
|
||||
This guide will help you get started with ReMe using the Model Context Protocol (MCP) interface for seamless
|
||||
integration with MCP-compatible clients.
|
||||
|
||||
## 🚀 What You'll Learn
|
||||
|
||||
- How to set up ExperienceMaker MCP server
|
||||
- Connect to the server using MCP clients
|
||||
- Run an agent and generate experiences via MCP
|
||||
- Retrieve and apply experiences through MCP tools
|
||||
- Build experience-enhanced agents with MCP integration
|
||||
- How to set up and configure ReMe MCP server
|
||||
- How to connect to the server using Python MCP clients
|
||||
- How to use task memory operations through MCP
|
||||
- How to build experience-enhanced agents with MCP integration
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
|
|
@ -23,14 +22,14 @@ integration with MCP-compatible clients.
|
|||
### Option 1: Install from PyPI (Recommended)
|
||||
|
||||
```bash
|
||||
pip install experiencemaker
|
||||
pip install reme-ai
|
||||
```
|
||||
|
||||
### Option 2: Install from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ExperienceMaker.git
|
||||
cd ExperienceMaker
|
||||
git clone https://github.com/modelscope/ReMe.git
|
||||
cd ReMe
|
||||
pip install .
|
||||
```
|
||||
|
||||
|
|
@ -39,77 +38,58 @@ pip install .
|
|||
Create a `.env` file in your project directory:
|
||||
|
||||
```bash
|
||||
# Required: LLM API configuration
|
||||
LLM_API_KEY="sk-xxx"
|
||||
LLM_BASE_URL="https://xxx.com/v1"
|
||||
FLOW_EMBEDDING_API_KEY=sk-xxxx
|
||||
FLOW_EMBEDDING_BASE_URL=https://xxxx/v1
|
||||
|
||||
# Required: Embedding model configuration
|
||||
EMBEDDING_MODEL_API_KEY="sk-xxx"
|
||||
EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# Optional: Elasticsearch configuration (if using Elasticsearch backend)
|
||||
ES_HOSTS="http://localhost:9200"
|
||||
FLOW_LLM_API_KEY=sk-xxxx
|
||||
FLOW_LLM_BASE_URL=https://xxxx/v1
|
||||
```
|
||||
|
||||
## 🚀 Start the MCP Server
|
||||
## 🚀 Building an MCP Server with ReMe
|
||||
|
||||
### Option 1: STDIO Transport (Recommended for MCP clients)
|
||||
ReMe provides a flexible framework for building MCP servers that can communicate using either STDIO or SSE (Server-Sent
|
||||
Events) transport protocols.
|
||||
|
||||
### Starting the MCP Server
|
||||
|
||||
#### Option 1: STDIO Transport (Recommended for MCP clients)
|
||||
|
||||
```bash
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=stdio \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
reme \
|
||||
backend=mcp \
|
||||
mcp.transport=stdio \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
### Option 2: SSE Transport (Server-Sent Events)
|
||||
#### Option 2: SSE Transport (Server-Sent Events)
|
||||
|
||||
```bash
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=sse \
|
||||
reme \
|
||||
backend=mcp \
|
||||
mcp.transport=sse \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
vector_store.default.backend=local
|
||||
```
|
||||
|
||||
The SSE server will start on `http://localhost:8001/sse`
|
||||
The SSE server will start on `http://localhost:8002/sse`
|
||||
|
||||
### Elasticsearch Backend
|
||||
### Configuring MCP Server for Claude Desktop
|
||||
|
||||
```bash
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=stdio \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=elasticsearch
|
||||
```
|
||||
|
||||
**Setup Elasticsearch:**
|
||||
|
||||
```bash
|
||||
export ES_HOSTS="http://localhost:9200"
|
||||
# Quick setup using Elastic's official script
|
||||
curl -fsSL https://elastic.co/start-local | sh
|
||||
```
|
||||
|
||||
📖 **Need Help?** Refer to [Vector Store Setup](vector_store_setup.md) for comprehensive deployment guidance.
|
||||
|
||||
## 🔧 Configure MCP Client
|
||||
|
||||
### Claude Desktop Configuration
|
||||
|
||||
Add to your Claude Desktop `claude_desktop_config.json`:
|
||||
To integrate with Claude Desktop, add the following configuration to your `claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"experiencemaker": {
|
||||
"command": "experiencemaker_mcp",
|
||||
"reme": {
|
||||
"command": "reme",
|
||||
"args": [
|
||||
"mcp_transport=stdio",
|
||||
"llm.default.model_name=qwen3-32b",
|
||||
"backend=mcp",
|
||||
"mcp.transport=stdio",
|
||||
"llm.default.model_name=qwen3-30b-a3b-thinking-2507",
|
||||
"embedding_model.default.model_name=text-embedding-v4",
|
||||
"vector_store.default.backend=local_file"
|
||||
]
|
||||
|
|
@ -118,453 +98,311 @@ Add to your Claude Desktop `claude_desktop_config.json`:
|
|||
}
|
||||
```
|
||||
|
||||
### Custom MCP Client Configuration
|
||||
This configuration:
|
||||
|
||||
If using a custom MCP client, connect to:
|
||||
1. Registers a new MCP server named "reme"
|
||||
2. Specifies the command to launch the server (`reme`)
|
||||
3. Configures the server to use STDIO transport
|
||||
4. Sets the LLM and embedding models to use
|
||||
5. Configures the vector store backend
|
||||
|
||||
- **STDIO**: Use subprocess to communicate with the server
|
||||
- **SSE**: Connect to `http://localhost:8001/sse`
|
||||
### Advanced Server Configuration Options
|
||||
|
||||
## 📝 Using ExperienceMaker MCP Tools
|
||||
For more advanced use cases, you can configure the server with additional parameters:
|
||||
|
||||
The MCP server exposes three main tools:
|
||||
```bash
|
||||
# Full configuration example
|
||||
reme \
|
||||
backend=mcp \
|
||||
mcp.transport=stdio \
|
||||
http_service.host=0.0.0.0 \
|
||||
http_service.port=8002 \
|
||||
llm.default.model_name=qwen3-30b-a3b-thinking-2507 \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=elasticsearch \
|
||||
```
|
||||
|
||||
- `retriever`: Retrieve experiences from workspace
|
||||
- `summarizer`: Transform trajectories into experiences
|
||||
- `vector_store`: Manage vector store operations
|
||||
## 🔌 Using Python Client to Call MCP Services
|
||||
|
||||
Note: The `workspace_id` serves as your experience storage namespace. Experiences in different workspaces remain
|
||||
completely isolated.
|
||||
The ReMe framework provides a Python client for interacting with MCP services. This section focuses specifically on
|
||||
using the `summary_task_memory` and `retrieve_task_memory` tools.
|
||||
|
||||
### 📊 Using the Summarizer Tool
|
||||
### Setting Up the Python MCP Client
|
||||
|
||||
Transform conversation trajectories into valuable experiences using batch summarization.
|
||||
First, install the required packages:
|
||||
|
||||
**Tool Parameters:**
|
||||
```bash
|
||||
pip install fastmcp dotenv
|
||||
```
|
||||
|
||||
- `traj_list`: List of trajectories (each containing messages and score)
|
||||
- `workspace_id`: Workspace identifier (default: "default")
|
||||
- `config`: Additional configuration parameters (optional)
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
Then, create a basic client connection:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from experiencemaker.schema.message import Message, Trajectory, Role
|
||||
from experiencemaker.schema.request import SummarizerRequest
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# MCP server URL (for SSE transport)
|
||||
MCP_URL = "http://0.0.0.0:8002/sse/"
|
||||
WORKSPACE_ID = "my_workspace"
|
||||
|
||||
|
||||
async def example_summarizer():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
# Create trajectory with conversation
|
||||
trajectory = Trajectory(
|
||||
messages=[
|
||||
Message(role=Role.USER, content="Hello, how can I solve a math problem?"),
|
||||
Message(role=Role.ASSISTANT, content="I'd be happy to help! What math problem are you working on?"),
|
||||
Message(role=Role.USER, content="What is 2+2?"),
|
||||
Message(role=Role.ASSISTANT, content="2+2 equals 4.")
|
||||
],
|
||||
score=1.0 # Success score
|
||||
)
|
||||
|
||||
request = SummarizerRequest(
|
||||
workspace_id="math_workspace",
|
||||
traj_list=[trajectory]
|
||||
)
|
||||
|
||||
response = await client.call_summarizer(request)
|
||||
print("Generated experiences:")
|
||||
for experience in response.experience_list:
|
||||
print(f"- {experience.content}")
|
||||
async def main():
|
||||
async with Client(MCP_URL) as client:
|
||||
# Your MCP operations will go here
|
||||
pass
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_summarizer())
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
</details>
|
||||
### Using the Task Memory Summarizer
|
||||
|
||||
<details>
|
||||
<summary><b>MCP Tool Call (JSON)</b></summary>
|
||||
The `summary_task_memory` tool transforms conversation trajectories into valuable task memories:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "summarizer",
|
||||
"arguments": {
|
||||
"traj_list": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, how can I solve a math problem?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I'd be happy to help! What math problem are you working on?"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is 2+2?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "2+2 equals 4."
|
||||
```python
|
||||
async def run_summary(client, messages):
|
||||
"""
|
||||
Generate a summary of conversation messages and create task memories
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
messages: List of message objects from a conversation
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"summary_task_memory",
|
||||
arguments={
|
||||
"workspace_id": "my_workspace",
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
]
|
||||
}
|
||||
],
|
||||
"score": 1.0
|
||||
)
|
||||
|
||||
# Parse the response
|
||||
import json
|
||||
response_data = json.loads(result.content)
|
||||
|
||||
# Extract memory list from response
|
||||
memory_list = response_data.get("metadata", {}).get("memory_list", [])
|
||||
print(f"Created memories: {memory_list}")
|
||||
|
||||
# Optionally save memories to file
|
||||
with open("task_memory.jsonl", "w") as f:
|
||||
f.write(json.dumps(memory_list, indent=2, ensure_ascii=False))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error running summary: {e}")
|
||||
```
|
||||
|
||||
### Using the Task Memory Retriever
|
||||
|
||||
The `retrieve_task_memory` tool allows you to retrieve relevant memories based on a query:
|
||||
|
||||
```python
|
||||
async def run_retrieve(client, query):
|
||||
"""
|
||||
Retrieve relevant task memories based on a query
|
||||
|
||||
Args:
|
||||
client: MCP client instance
|
||||
query: The query to retrieve relevant memories
|
||||
|
||||
Returns:
|
||||
String containing the retrieved memory answer
|
||||
"""
|
||||
try:
|
||||
result = await client.call_tool(
|
||||
"retrieve_task_memory",
|
||||
arguments={
|
||||
"workspace_id": "my_workspace",
|
||||
"query": query,
|
||||
}
|
||||
)
|
||||
|
||||
# Parse the response
|
||||
import json
|
||||
response_data = json.loads(result.content)
|
||||
|
||||
# Extract and return the answer
|
||||
answer = response_data.get("answer", "")
|
||||
print(f"Retrieved memory: {answer}")
|
||||
return answer
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error retrieving memory: {e}")
|
||||
return ""
|
||||
```
|
||||
|
||||
### Complete Memory-Augmented Agent Example
|
||||
|
||||
Here's a complete example showing how to build a memory-augmented agent using the MCP client:
|
||||
|
||||
```python
|
||||
import json
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# API configuration
|
||||
MCP_URL = "http://0.0.0.0:8002/sse/"
|
||||
WORKSPACE_ID = "test_workspace"
|
||||
|
||||
|
||||
async def run_agent(client, query):
|
||||
"""Run the agent with a specific query"""
|
||||
result = await client.call_tool(
|
||||
"react",
|
||||
arguments={"query": query}
|
||||
)
|
||||
|
||||
response_data = json.loads(result.content)
|
||||
answer = response_data.get("answer", "")
|
||||
messages = response_data.get("messages", [])
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
async def run_summary(client, messages):
|
||||
"""Generate task memories from conversation"""
|
||||
result = await client.call_tool(
|
||||
"summary_task_memory",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
]
|
||||
}
|
||||
],
|
||||
"workspace_id": "math_workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads(result.content)
|
||||
memory_list = response_data.get("metadata", {}).get("memory_list", [])
|
||||
|
||||
return memory_list
|
||||
|
||||
|
||||
async def run_retrieve(client, query):
|
||||
"""Retrieve relevant task memories"""
|
||||
result = await client.call_tool(
|
||||
"retrieve_task_memory",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"query": query,
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads(result.content)
|
||||
answer = response_data.get("answer", "")
|
||||
|
||||
return answer
|
||||
|
||||
|
||||
async def memory_augmented_workflow():
|
||||
"""Complete memory-augmented agent workflow"""
|
||||
query1 = "Analyze Xiaomi Corporation"
|
||||
query2 = "Analyze the company Tesla."
|
||||
|
||||
async with Client(MCP_URL) as client:
|
||||
# Step 1: Build initial memories with query2
|
||||
print(f"Building memories with: '{query2}'")
|
||||
messages = await run_agent(client, query=query2)
|
||||
|
||||
# Step 2: Summarize conversation to create memories
|
||||
print("Creating memories from conversation")
|
||||
memory_list = await run_summary(client, messages)
|
||||
print(f"Created {len(memory_list)} memories")
|
||||
|
||||
# Step 3: Retrieve relevant memories for query1
|
||||
print(f"Retrieving memories for: '{query1}'")
|
||||
retrieved_memory = await run_retrieve(client, query1)
|
||||
|
||||
# Step 4: Run agent with memory-augmented query
|
||||
print("Running memory-augmented agent")
|
||||
augmented_query = f"{retrieved_memory}\n\nUser Question:\n{query1}"
|
||||
final_messages = await run_agent(client, query=augmented_query)
|
||||
|
||||
# Extract the agent's final answer
|
||||
final_answer = ""
|
||||
for msg in final_messages:
|
||||
if msg.get("role") == "assistant" and msg.get("content"):
|
||||
final_answer = msg.get("content")
|
||||
break
|
||||
|
||||
print(f"Memory-augmented response: {final_answer}")
|
||||
|
||||
|
||||
# Run the workflow
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(memory_augmented_workflow())
|
||||
```
|
||||
|
||||
</details>
|
||||
### Managing Vector Store with MCP
|
||||
|
||||
### 🔍 Using the Retriever Tool
|
||||
|
||||
Intelligently search and retrieve the most relevant experiences from your workspace.
|
||||
|
||||
**Tool Parameters:**
|
||||
|
||||
- `query`: Search query string
|
||||
- `messages`: List of conversation messages (optional)
|
||||
- `top_k`: Number of top experiences to retrieve (default: 1)
|
||||
- `workspace_id`: Workspace identifier (default: "default")
|
||||
- `config`: Additional configuration parameters (optional)
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
You can also manage your vector store through MCP:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import RetrieverRequest
|
||||
async def manage_vector_store(client):
|
||||
# Delete a workspace
|
||||
await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "delete",
|
||||
}
|
||||
)
|
||||
|
||||
# Dump memories to disk
|
||||
await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "dump",
|
||||
"path": "./backups/",
|
||||
}
|
||||
)
|
||||
|
||||
async def example_retriever():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
request = RetrieverRequest(
|
||||
workspace_id="math_workspace",
|
||||
query="How to solve basic arithmetic problems?",
|
||||
top_k=3
|
||||
)
|
||||
|
||||
response = await client.call_retriever(request)
|
||||
print(f"Retrieved experiences: {response.experience_merged}")
|
||||
print(f"Experience list:")
|
||||
for exp in response.experience_list:
|
||||
print(f"- {exp.content}")
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_retriever())
|
||||
# Load memories from disk
|
||||
await client.call_tool(
|
||||
"vector_store",
|
||||
arguments={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "load",
|
||||
"path": "./backups/",
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>MCP Tool Call (JSON)</b></summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "retriever",
|
||||
"arguments": {
|
||||
"query": "How to solve basic arithmetic problems?",
|
||||
"top_k": 3,
|
||||
"workspace_id": "math_workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 💾 Using the Vector Store Tool
|
||||
|
||||
Manage vector store operations for workspace data.
|
||||
|
||||
**Tool Parameters:**
|
||||
|
||||
- `action`: Action to perform ("dump", "load", "delete", "copy")
|
||||
- `workspace_id`: Target workspace identifier
|
||||
- `src_workspace_id`: Source workspace (for copy operation)
|
||||
- `path`: File system path (for dump/load operations, default: "./")
|
||||
- `config`: Additional configuration parameters (optional)
|
||||
|
||||
#### Dump Experiences From Vector Store
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import VectorStoreRequest
|
||||
|
||||
|
||||
async def example_dump():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
request = VectorStoreRequest(
|
||||
workspace_id="math_workspace",
|
||||
action="dump",
|
||||
path="./backups/"
|
||||
)
|
||||
|
||||
response = await client.call_vector_store(request)
|
||||
print(f"Dump result: {response}")
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_dump())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>MCP Tool Call (JSON)</b></summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "vector_store",
|
||||
"arguments": {
|
||||
"action": "dump",
|
||||
"workspace_id": "math_workspace",
|
||||
"path": "./backups/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Load Experiences To Vector Store
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import VectorStoreRequest
|
||||
|
||||
|
||||
async def example_load():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
request = VectorStoreRequest(
|
||||
workspace_id="math_workspace",
|
||||
action="load",
|
||||
path="./backups/"
|
||||
)
|
||||
|
||||
response = await client.call_vector_store(request)
|
||||
print(f"Load result: {response}")
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_load())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Delete Workspace
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import VectorStoreRequest
|
||||
|
||||
|
||||
async def example_delete():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
request = VectorStoreRequest(
|
||||
workspace_id="math_workspace",
|
||||
action="delete"
|
||||
)
|
||||
|
||||
response = await client.call_vector_store(request)
|
||||
print(f"Delete result: {response}")
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_delete())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Copy Workspace
|
||||
|
||||
<details open>
|
||||
<summary><b>Python MCP Client Example</b></summary>
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import VectorStoreRequest
|
||||
|
||||
|
||||
async def example_copy():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
request = VectorStoreRequest(
|
||||
workspace_id="math_workspace_copy",
|
||||
action="copy",
|
||||
src_workspace_id="math_workspace"
|
||||
)
|
||||
|
||||
response = await client.call_vector_store(request)
|
||||
print(f"Copy result: {response}")
|
||||
|
||||
|
||||
# Run the example
|
||||
asyncio.run(example_copy())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔄 Complete MCP Workflow Example
|
||||
|
||||
Here's a complete example showing the full workflow:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from experiencemaker.service.mcp_client import MCPClient
|
||||
from experiencemaker.schema.request import SummarizerRequest, RetrieverRequest
|
||||
from experiencemaker.schema.message import Message, Trajectory, Role
|
||||
|
||||
async def complete_workflow():
|
||||
async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client:
|
||||
print("Available tools:", await client.list_tools())
|
||||
|
||||
# Step 1: Create experiences from trajectories
|
||||
trajectory = Trajectory(
|
||||
messages=[
|
||||
Message(role=Role.USER, content="How do I calculate compound interest?"),
|
||||
Message(role=Role.ASSISTANT,
|
||||
content="Compound interest is calculated using the formula A = P(1 + r/n)^(nt), where A is the final amount, P is the principal, r is the annual interest rate, n is the number of times interest is compounded per year, and t is the time in years."),
|
||||
Message(role=Role.USER, content="Can you give me an example?"),
|
||||
Message(role=Role.ASSISTANT,
|
||||
content="Sure! If you invest $1000 at 5% annual interest compounded monthly for 2 years: A = 1000(1 + 0.05/12)^(12*2) = $1104.94")
|
||||
],
|
||||
score=1.0
|
||||
)
|
||||
|
||||
summarizer_request = SummarizerRequest(
|
||||
workspace_id="finance_workspace",
|
||||
traj_list=[trajectory]
|
||||
)
|
||||
|
||||
summarizer_response = await client.call_summarizer(summarizer_request)
|
||||
print(f"Created {len(summarizer_response.experience_list)} experiences")
|
||||
|
||||
# Step 2: Retrieve relevant experiences
|
||||
retriever_request = RetrieverRequest(
|
||||
workspace_id="finance_workspace",
|
||||
query="How to calculate interest on investments?",
|
||||
top_k=2
|
||||
)
|
||||
|
||||
retriever_response = await client.call_retriever(retriever_request)
|
||||
print(f"Retrieved experiences: {retriever_response.experience_merged}")
|
||||
|
||||
|
||||
# Run the complete workflow
|
||||
asyncio.run(complete_workflow())
|
||||
```
|
||||
|
||||
## 🎭 Claude Desktop Integration
|
||||
|
||||
Once configured with Claude Desktop, you can directly ask Claude to use ExperienceMaker tools:
|
||||
|
||||
```
|
||||
Claude, please use the summarizer tool to create experiences from this conversation about solving math problems, then retrieve similar experiences when I ask about arithmetic.
|
||||
```
|
||||
|
||||
Claude will automatically call the appropriate MCP tools and provide contextually relevant responses based on your
|
||||
stored experiences.
|
||||
|
||||
## 🐛 Common Issues
|
||||
## 🐛 Common Issues and Troubleshooting
|
||||
|
||||
### MCP Server Won't Start
|
||||
|
||||
- Check if the required ports are available (for SSE transport)
|
||||
- Verify your API keys in `.env` file
|
||||
- Ensure Python version is 3.12+
|
||||
- Check MCP transport configuration
|
||||
|
||||
### MCP Client Connection Issues
|
||||
|
||||
- For STDIO: Ensure the command path is correct in your MCP client config
|
||||
- For SSE: Verify the server URL and port accessibility
|
||||
- Check firewall settings for SSE connections
|
||||
|
||||
### No Experiences Retrieved
|
||||
### No Memories Retrieved
|
||||
|
||||
- Make sure you've run the summarizer tool first to create experiences
|
||||
- Make sure you've run the summarizer tool first to create memories
|
||||
- Check if workspace_id matches between operations
|
||||
- Verify vector store backend is properly configured
|
||||
|
||||
### API Connection Errors
|
||||
|
||||
- Confirm LLM_BASE_URL and API keys are correct
|
||||
- Test API access independently
|
||||
- Check network connectivity
|
||||
|
||||
## 🔧 Advanced Configuration
|
||||
|
||||
### Custom MCP Client Setup
|
||||
|
||||
```python
|
||||
# For STDIO transport
|
||||
async with MCPClient(enable_sse=False) as client:
|
||||
# Your MCP operations here
|
||||
pass
|
||||
|
||||
# For SSE transport with custom URL
|
||||
async with MCPClient(base_url="http://custom-host:8001/sse") as client:
|
||||
# Your MCP operations here
|
||||
pass
|
||||
```
|
||||
|
||||
### Server Configuration Options
|
||||
|
||||
```bash
|
||||
# Full configuration example
|
||||
experiencemaker_mcp \
|
||||
mcp_transport=stdio \
|
||||
http_service.host=0.0.0.0 \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
llm.default.api_key=${LLM_API_KEY} \
|
||||
llm.default.base_url=${LLM_BASE_URL} \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
embedding_model.default.api_key=${EMBEDDING_MODEL_API_KEY} \
|
||||
embedding_model.default.base_url=${EMBEDDING_MODEL_BASE_URL} \
|
||||
vector_store.default.backend=elasticsearch \
|
||||
vector_store.default.host=localhost \
|
||||
vector_store.default.port=9200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
🎯 **You're all set!** You now have a working ExperienceMaker MCP setup that can seamlessly integrate with MCP-compatible
|
||||
clients and learn from interactions to improve over time through the standardized MCP protocol.
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
- Explore the [Configuration Guide](configuration_guide.md) for advanced customization
|
||||
- Check out [cookbook examples](../cookbook/) for practical implementations
|
||||
- Learn about [Vector Store Setup](vector_store_setup.md) for production deployments
|
||||
- Review the [Operations Documentation](operations_documentation.md) for maintenance procedures
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Operations Documentation
|
||||
|
||||
This document provides an overview of all operations in the ExperienceMaker framework.
|
||||
|
||||
## Operations Overview
|
||||
|
||||
| Op Class | Registered Backend | Description | Parameters |
|
||||
|-----------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `BuildQueryOp` | `build_query_op` | Constructs retrieval queries from user requests. If request.query exists, uses it directly. If only messages are provided, can either use LLM-based query construction or create a simple summary from the last 3 messages (200 chars each). Sets search query and messages in context for downstream operations. | `op.build_query_op.params.enable_llm_build = true/false` - Enable LLM-based query construction from messages. When false, creates simple summary from last 3 messages |
|
||||
| `RerankExperienceOp` | `rerank_experience_op` | Performs two-stage experience reranking: (1) LLM-based intelligent reranking using relevance evaluation, (2) Score-based filtering using confidence and validation scores. Returns top-k results after filtering. Handles parsing of LLM reranking responses in JSON format with fallback to text parsing. | `op.rerank_experience_op.params.enable_llm_rerank = true` - Enable LLM-based reranking<br>`op.rerank_experience_op.params.enable_score_filter = false` - Enable score-based filtering<br>`op.rerank_experience_op.params.min_score_threshold = 0.3` - Minimum combined score threshold for filtering<br>`op.rerank_experience_op.params.top_k = 5` - Number of top experiences to return after reranking |
|
||||
| `RewriteExperienceOp` | `rewrite_experience_op` | Intelligently rewrites experience context for better task relevance. Extracts current context from recent messages (last 3), formats experiences, and optionally uses LLM to rewrite context based on current query and conversation history. Handles JSON response parsing with fallback to original content. Generates structured context messages with "When to use" and "Content" sections. | `op.rewrite_experience_op.params.enable_llm_rewrite = true` - Enable LLM-based context rewriting to make experiences more relevant and actionable for current task |
|
||||
| `MergeExperienceOp` | `merge_experience_op` | Formats multiple experiences into a single structured context message. Creates "Previous Experience" header followed by bullet-pointed list of experiences with "when_to_use" and "content" fields. Adds guidance text encouraging comprehensive response using helpful parts from experiences. Simple concatenation-based approach without LLM processing. | No configurable parameters |
|
||||
| `TrajectoryPreprocessOp` | `trajectory_preprocess_op` | Validates and classifies trajectories based on success threshold scoring. Separates trajectories into success/failure categories and sets up context variables (success_trajectories, failure_trajectories, all_trajectories) for downstream extraction operations. Essential preprocessing step for all summarizer operations. | `op.trajectory_preprocess_op.params.success_threshold = 1.0` - Score threshold to classify trajectories as successful. Trajectories with scores >= threshold are classified as success |
|
||||
| `TrajectorySegmentationOp` | `trajectory_segmentation_op` | Uses LLM to segment trajectories into meaningful step sequences based on logical breakpoints. Supports selective segmentation of success, failure, or all trajectories. Parses LLM responses in JSON format with fallback to number extraction. Stores segmentation information in trajectory metadata for downstream operations. Formats trajectory content with step numbers and role information. | `op.trajectory_segmentation_op.params.segment_target = "all"` - Which trajectories to segment ("all", "success", "failure") |
|
||||
| `ExperienceValidationOp` | `experience_validation_op` | Validates extracted experiences using LLM-based quality assessment. Evaluates experiences for actionability, accuracy, relevance, clarity, and uniqueness. Uses parallel processing for efficiency. Parses JSON validation responses with score and validity flags. Filters experiences based on validation threshold and removes invalid ones with detailed logging of rejection reasons. | `op.experience_validation_op.params.validation_threshold = 0.5` - Minimum validation score threshold for experience acceptance. Experiences with scores below this threshold are filtered out |
|
||||
| `ExperienceDeduplicationOp` | `experience_deduplication_op` | Removes duplicate experiences using embedding-based similarity analysis. Compares against both existing vector store experiences and current batch experiences. Calculates cosine similarity between experience embeddings and filters duplicates above similarity threshold. Handles embedding generation failures gracefully and provides detailed logging of deduplication decisions. | `op.experience_deduplication_op.params.similarity_threshold = 0.5` - Cosine similarity threshold for duplicate detection<br>`op.experience_deduplication_op.params.max_existing_experiences = 1000` - Maximum number of existing experiences to retrieve and compare against for deduplication |
|
||||
| `ComparativeExtractionOp` | `comparative_extraction_op` | Extracts insights by comparing different trajectory outcomes. Supports two comparison modes: (1) Soft comparison between highest and lowest scoring trajectories, (2) Hard comparison between similar success/failure step sequences using embedding-based similarity matching. Uses parallel processing and handles trajectory segmentation data when available. | `op.comparative_extraction_op.params.enable_soft_comparison = true` - Enable highest vs lowest score comparison<br>`op.comparative_extraction_op.params.enable_similarity_comparison = false` - Enable success vs failure similarity comparison<br>`op.comparative_extraction_op.params.max_similarity_sequences = 5` - Maximum sequences to compare for similarity<br>`op.comparative_extraction_op.params.similarity_threshold = 0.3` - Similarity threshold for step sequence matching<br>`op.comparative_extraction_op.params.max_similarity_pairs = 3` - Maximum similar pairs to extract experiences from |
|
||||
| `SimpleSummaryOp` | `simple_summary_op` | Generates basic experiences from individual trajectories using LLM-based analysis. Classifies trajectories as success/failure based on score threshold and creates structured experiences with when_to_use conditions and content. Parses JSON responses with robust error handling and validation. Uses parallel processing for multiple trajectories. | `op.simple_summary_op.params.success_score_threshold = 0.9` - Score threshold to classify trajectory as successful for experience extraction |
|
||||
| `SuccessExtractionOp` | `success_extraction_op` | Extracts actionable experiences from successful trajectories and their segments. Processes both segmented step sequences (when available) and entire trajectories. Uses parallel processing for efficiency. Merges message content and extracts trajectory context for rich experience generation. Creates TextExperience objects with proper metadata including workspace and author information. | No configurable parameters |
|
||||
| `FailureExtractionOp` | `failure_extraction_op` | Extracts learning experiences from failed trajectories to identify failure patterns and pitfalls. Similar to SuccessExtractionOp but focuses on failure analysis. Processes segmented sequences when available or entire trajectories. Uses parallel processing and creates structured experiences with proper metadata. Helps identify common failure modes and prevention strategies. | No configurable parameters |
|
||||
| `UpdateVectorStoreOp` | `update_vector_store_op` | Manages vector store updates through insert and delete operations. Handles deletion of experiences by ID list and insertion of new experience lists. Converts BaseExperience objects to VectorNode format for storage. Operates on workspace-specific vector databases with detailed logging of operation sizes and IDs. Supports batch operations for efficiency. | No configurable parameters - operations controlled by experience_list and deleted_experience_ids in response context |
|
||||
| `RecallVectorStoreOp` | `recall_vector_store_op` | Retrieves relevant experiences from vector store using semantic search. Performs content-based deduplication to avoid returning identical experiences. Supports optional score-based filtering to ensure quality results. Converts VectorNode results back to BaseExperience objects. Uses search query from context set by BuildQueryOp. | `op.recall_vector_store_op.params.threshold_score = <float>` - Optional minimum similarity score threshold for filtering search results. Results below this score are excluded |
|
||||
| `VectorStoreActionOp` | `vector_store_action_op` | Performs administrative operations on vector store workspaces. Supports four actions: (1) copy - duplicates workspace content, (2) delete - removes entire workspace, (3) dump - exports workspace to file with experience conversion, (4) load - imports workspace from file with node conversion. Handles callback functions for data transformation during dump/load operations. | Action-specific parameters: `request.action` ("copy"/"delete"/"dump"/"load"), `request.workspace_id` (target workspace), `request.src_workspace_id` (source workspace for copy), `request.path` (file path for dump/load) |
|
||||
| `ReactV1Op` | `react_v1_op` | Implements ReAct (Reasoning and Acting) agent framework for interactive problem-solving. Manages iterative reasoning-action cycles with configurable tools and step limits. Handles tool execution with parallel processing and result collection. Supports terminate tool for early stopping. Formats conversations with role prompts, tool responses, and final prompts. Includes built-in safeguards for missing tools and infinite loops. | `op.react_v1_op.params.max_steps = 10` - Maximum number of reasoning/action steps before termination<br>`op.react_v1_op.params.tool_names = "code_tool,dashscope_search_tool,terminate_tool"` - Comma-separated list of available tools from the tool registry |
|
||||
|
||||
124
doc/personal_memory/personal_memory.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# Personal Memory in Reme
|
||||
|
||||
## Configuration Logic
|
||||
|
||||
Reme's personal memory system consists of two main components: retrieval and summarization. The configuration for these components is defined in the default.yaml file.
|
||||
|
||||
### Retrieval Configuration (`retrieve_personal_memory`)
|
||||
|
||||
```yaml
|
||||
retrieve_personal_memory:
|
||||
flow_content: set_query_op >> (extract_time_op | (retrieve_memory_op >> semantic_rank_op)) >> fuse_rerank_op
|
||||
```
|
||||
|
||||
This flow performs the following operations:
|
||||
1. `set_query_op`: Prepares the query for memory retrieval
|
||||
2. Parallel paths:
|
||||
- `extract_time_op`: Extracts time-related information from the query
|
||||
- `retrieve_memory_op >> semantic_rank_op`: Retrieves memories and ranks them semantically
|
||||
3. `fuse_rerank_op`: Combines and reranks the results for final output
|
||||
|
||||
### Summarization Configuration (`summary_personal_memory`)
|
||||
|
||||
```yaml
|
||||
summary_personal_memory:
|
||||
flow_content: info_filter_op >> (get_observation_op | get_observation_with_time_op | load_today_memory_op) >> contra_repeat_op >> update_vector_store_op
|
||||
```
|
||||
|
||||
This flow performs the following operations:
|
||||
1. `info_filter_op`: Filters incoming information to extract relevant personal details
|
||||
2. Parallel paths for observation extraction:
|
||||
- `get_observation_op`: Extracts general observations
|
||||
- `get_observation_with_time_op`: Extracts observations with time context
|
||||
- `load_today_memory_op`: Loads memories from the current day
|
||||
3. `contra_repeat_op`: Removes contradictions and repetitions
|
||||
4. `update_vector_store_op`: Stores the processed memories in the vector database
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The following example demonstrates how to use personal memory in MemoryScope:
|
||||
|
||||
### 1. Setup
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
import aiohttp
|
||||
|
||||
# API base URL (default is http://0.0.0.0:8002)
|
||||
base_url = "http://0.0.0.0:8002"
|
||||
workspace_id = "personal_memory_demo"
|
||||
```
|
||||
|
||||
### 2. Clear Existing Memories
|
||||
|
||||
```python
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Delete existing workspace memories
|
||||
async with session.post(
|
||||
f"{base_url}/vector_store",
|
||||
json={
|
||||
"action": "delete",
|
||||
"workspace_id": workspace_id,
|
||||
},
|
||||
headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
result = await response.json()
|
||||
```
|
||||
|
||||
### 3. Create Conversation with Personal Information
|
||||
|
||||
```python
|
||||
# Example conversation with personal details
|
||||
messages = [
|
||||
{"role": "user", "content": "My name is John Smith, I'm 28 years old"},
|
||||
{"role": "assistant", "content": "Nice to meet you, John!"},
|
||||
{"role": "user", "content": "I'm a software engineer working with Python"},
|
||||
{"role": "assistant", "content": "I see, you're a Python engineer."},
|
||||
# Additional conversation messages...
|
||||
]
|
||||
```
|
||||
|
||||
### 4. Summarize Personal Memories
|
||||
|
||||
```python
|
||||
async with session.post(
|
||||
f"{base_url}/summary_personal_memory",
|
||||
json={
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
],
|
||||
"workspace_id": workspace_id,
|
||||
},
|
||||
headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
result = await response.json()
|
||||
```
|
||||
|
||||
### 5. Retrieve Personal Memories
|
||||
|
||||
```python
|
||||
# Example queries to retrieve personal information
|
||||
queries = [
|
||||
"What's my name and age?",
|
||||
"What do I do for work?",
|
||||
"What are my hobbies?"
|
||||
]
|
||||
|
||||
for query in queries:
|
||||
async with session.post(
|
||||
f"{base_url}/retrieve_personal_memory",
|
||||
json={
|
||||
"query": query,
|
||||
"workspace_id": workspace_id,
|
||||
},
|
||||
headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
result = await response.json()
|
||||
print(f"Query: {query}")
|
||||
print(f"Answer: {result.get('answer', '')}")
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
For a complete working example, refer to `/cookbook/simple_demo/use_personal_memory_demo.py` in the Reme repository.
|
||||
132
doc/personal_memory/personal_retrieve_ops.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# Personal Memory Retrieve Ops
|
||||
|
||||
## SetQueryOp
|
||||
|
||||
### Functionality
|
||||
`SetQueryOp` prepares the query for memory retrieval by setting the query and its associated timestamp into the context. It's the first operation in the personal memory retrieval flow.
|
||||
|
||||
### Parameters
|
||||
- `op.set_query_op.params.timestamp`: (Optional) Integer timestamp to use instead of the current time. If not provided, the current timestamp will be used.
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Takes the query from the context (which is guaranteed to exist as a flow input requirement)
|
||||
2. Sets a timestamp (either current time or from parameters)
|
||||
3. Stores the query and timestamp as a tuple in the context for downstream operations
|
||||
|
||||
## ExtractTimeOp
|
||||
|
||||
### Functionality
|
||||
`ExtractTimeOp` identifies and extracts time-related information from the query. It uses an LLM to analyze the query text and determine any temporal references or constraints.
|
||||
|
||||
### Parameters
|
||||
- `op.extract_time_op.params.language`: Language for time extraction (defaults to "en")
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Checks if the query contains datetime keywords
|
||||
2. If time-related words are found, it prepares a prompt for the LLM with:
|
||||
- System instructions
|
||||
- Few-shot examples
|
||||
- The user's query and current time
|
||||
3. Parses the LLM response to extract time information (year, month, day, etc.)
|
||||
4. Stores the extracted time dictionary in the context for downstream operations
|
||||
|
||||
## RetrieveMemoryOp
|
||||
|
||||
### Functionality
|
||||
`RetrieveMemoryOp` retrieves memories from the vector store based on the query. It extends the `RecallVectorStoreOp` class to provide memory retrieval functionality.
|
||||
|
||||
### Parameters
|
||||
- `op.retrieve_memory_op.params.recall_key`: Key in the context to use as the query (default: "query")
|
||||
- `op.retrieve_memory_op.params.top_k`: Maximum number of memories to retrieve (default: 3)
|
||||
- `op.retrieve_memory_op.params.threshold_score`: (Optional) Minimum similarity score for memories (filters out memories below this threshold)
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Retrieves the query from the context
|
||||
2. Searches the vector store for relevant memories based on the query
|
||||
3. Removes duplicate memories
|
||||
4. Filters memories by threshold score if specified
|
||||
5. Stores the retrieved memories in the context for downstream operations
|
||||
|
||||
## SemanticRankOp
|
||||
|
||||
### Functionality
|
||||
`SemanticRankOp` ranks memories based on their semantic relevance to the query using an LLM. This improves the quality of retrieved memories by considering deeper semantic relationships beyond vector similarity.
|
||||
|
||||
### Parameters
|
||||
- `op.semantic_rank_op.params.enable_ranker`: Whether to enable semantic ranking (default: true)
|
||||
- `op.semantic_rank_op.params.output_memory_max_count`: Maximum number of memories to output (default: 10)
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Retrieves the memory list from the context
|
||||
2. If ranking is enabled and there are more memories than the output limit:
|
||||
- Removes duplicates based on content
|
||||
- Formats memories for LLM ranking
|
||||
- Asks the LLM to rank memories by relevance on a scale of 0.0 to 1.0
|
||||
- Parses the ranking results and applies scores to memories
|
||||
3. Sorts memories by score
|
||||
4. Stores the ranked memories in the context for downstream operations
|
||||
|
||||
## FuseRerankOp
|
||||
|
||||
### Functionality
|
||||
`FuseRerankOp` performs the final reranking of memories by combining multiple factors: semantic scores, memory types, and temporal relevance. It also formats the final output.
|
||||
|
||||
### Parameters
|
||||
- `op.fuse_rerank_op.params.fuse_score_threshold`: Minimum score threshold for memories (default: 0.1)
|
||||
- `op.fuse_rerank_op.params.fuse_ratio_dict`: Dictionary of memory type to score multiplier ratios (default: {"conversation": 0.5, "observation": 1, "obs_customized": 1.2, "insight": 2.0})
|
||||
- `op.fuse_rerank_op.params.fuse_time_ratio`: Score multiplier for time-relevant memories (default: 2.0)
|
||||
- `op.fuse_rerank_op.params.output_memory_max_count`: Maximum number of memories to output (default: 5)
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Retrieves extracted time information and memory list from the context
|
||||
2. For each memory:
|
||||
- Checks if the memory score is above the threshold
|
||||
- Applies a type-based adjustment factor based on the memory type
|
||||
- Determines time relevance by matching memory time metadata with extracted time
|
||||
- Calculates the final score by multiplying the original score by type and time factors
|
||||
3. Sorts memories by the reranked scores
|
||||
4. Selects the top-K memories based on the output limit
|
||||
5. Formats memories for output with timestamps if available
|
||||
6. Stores both the formatted output and the memory list in the context
|
||||
|
||||
## PrintMemoryOp
|
||||
|
||||
### Functionality
|
||||
`PrintMemoryOp` formats the retrieved memories for display to the user. It provides a clean, structured representation of the memory content.
|
||||
|
||||
### Parameters
|
||||
No specific parameters for this operation.
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Retrieves the memory list from the context
|
||||
2. Formats each memory with:
|
||||
- Memory index
|
||||
- When to use information
|
||||
- Content
|
||||
- Additional metadata (if available)
|
||||
3. Joins the formatted memories into a single string
|
||||
4. Stores the formatted string in the context as the response answer
|
||||
|
||||
## ReadMessageOp
|
||||
|
||||
### Functionality
|
||||
`ReadMessageOp` fetches unmemorized chat messages from the context. This is useful for retrieving recent conversations that haven't been processed into memories yet.
|
||||
|
||||
### Parameters
|
||||
- `op.read_message_op.params.contextual_msg_max_count`: Maximum number of contextual messages to retrieve (default: 10)
|
||||
|
||||
### Implementation Details
|
||||
The operation:
|
||||
1. Retrieves chat messages from the context
|
||||
2. Filters for messages that:
|
||||
- Are not marked as memorized
|
||||
- Contain the target name
|
||||
3. Flattens the messages into a single list
|
||||
4. Sorts messages by creation time if available
|
||||
5. Stores the filtered messages back in the context
|
||||
145
doc/personal_memory/personal_summary_ops.md
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# Personal Memory Summary Ops
|
||||
|
||||
## InfoFilterOp
|
||||
|
||||
### Purpose
|
||||
Filters messages based on information content scores, retaining only those that include significant information about the user.
|
||||
|
||||
### Parameters
|
||||
- `op.info_filter_op.params.preserved_scores`: Comma-separated string of scores to preserve (default: "2,3")
|
||||
- `op.info_filter_op.params.info_filter_msg_max_size`: Maximum size of messages to process (default: 200)
|
||||
|
||||
### Description
|
||||
This operation analyzes messages to determine which ones contain valuable personal information. It uses an LLM to score each message on a scale of 0-3:
|
||||
- 0: No user information
|
||||
- 1: Hypothetical or fictional content
|
||||
- 2: General or time-sensitive information
|
||||
- 3: Clear, important information or explicitly requested records
|
||||
|
||||
Only messages with scores specified in `preserved_scores` are retained. Messages are also filtered to exclude those already memorized and to only include messages from the user.
|
||||
|
||||
## GetObservationOp
|
||||
|
||||
### Purpose
|
||||
Extracts general observations about the user from messages that don't contain time-related information.
|
||||
|
||||
### Parameters
|
||||
No specific parameters for this operation.
|
||||
|
||||
### Description
|
||||
This operation processes messages that don't contain time-related keywords. It uses an LLM to extract meaningful observations about the user from these messages. Each observation includes:
|
||||
- Content: The actual observation text
|
||||
- Keywords: Tags that indicate when this observation might be relevant
|
||||
- Source message: The original message that led to this observation
|
||||
|
||||
The operation creates `PersonalMemory` objects with observation type "personal_info" for each extracted observation.
|
||||
|
||||
## GetObservationWithTimeOp
|
||||
|
||||
### Purpose
|
||||
Extracts observations with time context from messages that contain time-related information.
|
||||
|
||||
### Parameters
|
||||
No specific parameters for this operation.
|
||||
|
||||
### Description
|
||||
This operation is the counterpart to `GetObservationOp` but focuses specifically on messages containing time-related keywords. It extracts observations while preserving the time context, which is important for memories related to schedules, appointments, or time-specific preferences.
|
||||
|
||||
The operation creates `PersonalMemory` objects with observation type "personal_info_with_time" for each extracted observation, including the time information in the metadata.
|
||||
|
||||
## LoadTodayMemoryOp
|
||||
|
||||
### Purpose
|
||||
Loads memories created today from the vector store to prevent duplication and enable updating of recent memories.
|
||||
|
||||
### Parameters
|
||||
- `op.load_today_memory_op.params.top_k`: Maximum number of memories to retrieve (default: 50)
|
||||
|
||||
### Description
|
||||
This operation retrieves memories created on the current day using vector store search with date filtering. It converts vector nodes to memory objects and makes them available for deduplication in subsequent operations. This helps ensure that new observations don't create redundant memories for information already captured earlier in the day.
|
||||
|
||||
## ContraRepeatOp
|
||||
|
||||
### Purpose
|
||||
Identifies and removes contradictory or repetitive information from the collected memories.
|
||||
|
||||
### Parameters
|
||||
- `op.contra_repeat_op.params.contra_repeat_max_count`: Maximum number of memories to process (default: 50)
|
||||
- `op.contra_repeat_op.params.enable_contra_repeat`: Whether to enable contradiction/repetition checking (default: true)
|
||||
|
||||
### Description
|
||||
This operation analyzes the combined memories from previous operations (observation_memories, observation_memories_with_time, today_memories) to identify contradictions or redundancies. It uses an LLM to evaluate each memory and mark it as:
|
||||
- "Contradiction": Contradicts other memories
|
||||
- "Contained": Redundant as the information is already contained in other memories
|
||||
- "None": Unique and should be kept
|
||||
|
||||
Memories marked as contradictory or contained are filtered out, and their IDs are tracked for deletion from the vector store.
|
||||
|
||||
## LongContraRepeatOp
|
||||
|
||||
### Purpose
|
||||
Performs more sophisticated contradiction and redundancy analysis for longer-term memory management.
|
||||
|
||||
### Parameters
|
||||
- `op.long_contra_repeat_op.params.long_contra_repeat_max_count`: Maximum number of memories to process (default: 50)
|
||||
- `op.long_contra_repeat_op.params.enable_long_contra_repeat`: Whether to enable this operation (default: true)
|
||||
|
||||
### Description
|
||||
This operation extends the basic contradiction analysis of `ContraRepeatOp` with the ability to resolve conflicts by modifying contradictory memories rather than simply removing them. It's particularly useful for managing long-term personal memories where information might evolve over time.
|
||||
|
||||
For contradictory memories, it can either:
|
||||
- Modify the content to resolve the contradiction
|
||||
- Remove the memory if it's completely invalidated
|
||||
- Keep the most accurate/recent information
|
||||
|
||||
## UpdateInsightOp
|
||||
|
||||
### Purpose
|
||||
Updates existing insight values based on new observations.
|
||||
|
||||
### Parameters
|
||||
- `op.update_insight_op.params.update_insight_threshold`: Minimum relevance score threshold (default: 0.3)
|
||||
- `op.update_insight_op.params.update_insight_max_count`: Maximum number of insights to update (default: 5)
|
||||
|
||||
### Description
|
||||
This operation integrates new observations into existing insights about the user. It:
|
||||
1. Scores insight memories based on relevance to new observations
|
||||
2. Selects the top insights that meet the relevance threshold
|
||||
3. Updates each selected insight using an LLM to incorporate the new information
|
||||
4. Creates updated insight memories with the original ID but new content
|
||||
|
||||
This helps maintain accurate and up-to-date insights as new information about the user becomes available.
|
||||
|
||||
## GetReflectionSubjectOp
|
||||
|
||||
### Purpose
|
||||
Generates reflection subjects (topics) from personal memories for insight extraction.
|
||||
|
||||
### Parameters
|
||||
- `op.get_reflection_subject_op.params.reflect_obs_cnt_threshold`: Minimum number of memories required for reflection (default: 10)
|
||||
- `op.get_reflection_subject_op.params.reflect_num_questions`: Maximum number of new subjects to generate (default: 3)
|
||||
|
||||
### Description
|
||||
This operation analyzes a collection of personal memories to identify potential topics for reflection and insight generation. It:
|
||||
1. Checks if there are sufficient memories for meaningful reflection
|
||||
2. Extracts existing insight subjects to avoid duplication
|
||||
3. Uses an LLM to generate new reflection subjects based on memory content
|
||||
4. Creates insight memory objects for these new subjects
|
||||
|
||||
The generated subjects serve as focal points for organizing and synthesizing personal information about the user.
|
||||
|
||||
## UpdateVectorStoreOp
|
||||
|
||||
### Purpose
|
||||
Stores the processed memories in the vector database and removes deleted memories.
|
||||
|
||||
### Parameters
|
||||
No specific parameters for this operation.
|
||||
|
||||
### Description
|
||||
This operation is the final step in the personal memory summarization flow. It:
|
||||
1. Deletes memories that were marked for removal (contradictory or redundant)
|
||||
2. Inserts new or updated memories into the vector store
|
||||
3. Records the number of deleted and inserted memories
|
||||
|
||||
This ensures that the vector store remains up-to-date with the latest processed memories.
|
||||
|
|
@ -1,559 +0,0 @@
|
|||
# ExperienceMaker Quick Start Guide
|
||||
This guide will help you get started with ExperienceMaker quickly using practical examples.
|
||||
|
||||
## 🚀 What You'll Learn
|
||||
- How to set up ExperienceMaker service
|
||||
- Run an agent and generate experiences
|
||||
- Retrieve and apply experiences to new tasks
|
||||
- Build experience-enhanced agents
|
||||
|
||||
## 📋 Prerequisites
|
||||
- Python 3.12+
|
||||
- LLM API access (OpenAI or compatible)
|
||||
- Embedding model API access
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
### Option 1: Install from PyPI (Recommended)
|
||||
|
||||
```bash
|
||||
pip install experiencemaker
|
||||
```
|
||||
|
||||
### Option 2: Install from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/modelscope/ExperienceMaker.git
|
||||
cd ExperienceMaker
|
||||
pip install .
|
||||
```
|
||||
|
||||
## ⚙️ Environment Setup
|
||||
Create a `.env` file in your project directory:
|
||||
|
||||
```bash
|
||||
# Required: LLM API configuration
|
||||
LLM_API_KEY="sk-xxx"
|
||||
LLM_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# Required: Embedding model configuration
|
||||
EMBEDDING_MODEL_API_KEY="sk-xxx"
|
||||
EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1"
|
||||
|
||||
# Optional: Elasticsearch configuration (if using Elasticsearch backend)
|
||||
|
||||
```
|
||||
|
||||
## 🚀 Start the Service
|
||||
For testing, use the `local_file` backend:
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=local_file
|
||||
```
|
||||
The service will start on `http://localhost:8001`
|
||||
|
||||
### Elasticsearch Backend
|
||||
```bash
|
||||
experiencemaker \
|
||||
http_service.port=8001 \
|
||||
llm.default.model_name=qwen3-32b \
|
||||
embedding_model.default.model_name=text-embedding-v4 \
|
||||
vector_store.default.backend=elasticsearch
|
||||
```
|
||||
|
||||
**Setup Elasticsearch:**
|
||||
```bash
|
||||
export ES_HOSTS="http://localhost:9200"
|
||||
# Quick setup using Elastic's official script
|
||||
curl -fsSL https://elastic.co/start-local | sh
|
||||
```
|
||||
|
||||
📖 **Need Help?** Refer to [Vector Store Setup](../../doc/vector_store_setup.md) for comprehensive deployment guidance.
|
||||
|
||||
## 📝 Your First ExperienceMaker Script
|
||||
|
||||
Here's how to get started!
|
||||
Note the `workspace_id` serves as your experience storage namespace. Experiences in different workspaces remain
|
||||
completely isolated and cannot access each other.
|
||||
|
||||
### 📊 Call Summarizer Examples
|
||||
|
||||
Transform conversation trajectories into valuable experiences using batch summarization. Each trajectory contains:
|
||||
|
||||
- **Message**: Complete conversation history between user and agent
|
||||
- **Score**: Performance rating (0-1 scale, where 0=failure, 1=success)
|
||||
|
||||
The summarizer analyzes these trajectories to extract actionable insights and patterns for future interactions.
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/summarizer", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"traj_list": [
|
||||
{"messages": [{"role": "user", "content": "hello world"}], "score": 1.0}
|
||||
]
|
||||
})
|
||||
|
||||
experience_list = response.json()["experience_list"]
|
||||
for experience in experience_list:
|
||||
print(experience)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/summarizer" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"traj_list": [
|
||||
{
|
||||
"messages": [{"role": "user", "content": "hello world"}],
|
||||
"score": 1.0
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function callSummarizer() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/summarizer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
traj_list: [
|
||||
{
|
||||
messages: [{ role: "user", content: "hello world" }],
|
||||
score: 1.0
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const experienceList = data.experience_list;
|
||||
|
||||
experienceList.forEach(experience => {
|
||||
console.log(experience);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
callSummarizer();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 🔍 Call Retriever Examples
|
||||
|
||||
Intelligently search and retrieve the most relevant experiences from your workspace to enhance decision-making. The retriever:
|
||||
|
||||
- **Finds** the top-k most similar experiences based on semantic similarity to your query
|
||||
- **Returns** pre-assembled context ready for immediate use, or raw experience data for custom processing
|
||||
- **Leverages** your workspace's accumulated knowledge to provide contextually relevant insights
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/retriever", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"query": "what is the meaning of life?",
|
||||
"top_k": 1,
|
||||
})
|
||||
|
||||
experience_merged: str = response.json()["experience_merged"]
|
||||
print(f"experience_merged={experience_merged}")
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/retriever" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"query": "what is the meaning of life?",
|
||||
"top_k": 1
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function callRetriever() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/retriever', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
query: "what is the meaning of life?",
|
||||
top_k: 1
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const experienceMerged = data.experience_merged;
|
||||
|
||||
console.log(`experience_merged=${experienceMerged}`);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
callRetriever();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 💾 Dump Experiences From Vector Store
|
||||
|
||||
Export and backup your valuable experience data for archival, analysis, or migration purposes. This operation:
|
||||
|
||||
- **Extracts** all experiences from the specified workspace in the vector store
|
||||
- **Saves** them to a structured JSONL file at `{path}/{workspace_id}.jsonl`
|
||||
- **Preserves** complete experience metadata and embeddings for future restoration
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "dump",
|
||||
"path": "./",
|
||||
})
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "dump",
|
||||
"path": "./"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function dumpExperiences() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "dump",
|
||||
path: "./"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
dumpExperiences();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 📥 Load Experiences To Vector Store
|
||||
|
||||
Import and restore previously exported experience data to populate your workspace with existing knowledge. This operation:
|
||||
|
||||
- **Reads** experience data from the JSONL file located at `{path}/{workspace_id}.jsonl`
|
||||
- **Reconstructs** the vector embeddings and indexes them in the specified workspace
|
||||
- **Enables** immediate access to imported experiences for retrieval and decision-making
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "load",
|
||||
"path": "./",
|
||||
})
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "load",
|
||||
"path": "./"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function loadExperiences() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "load",
|
||||
path: "./"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
loadExperiences();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### 🗑️ Delete Workspace
|
||||
|
||||
Permanently remove a workspace and all its associated experience data when it's no longer needed. This operation:
|
||||
|
||||
- **Removes** all experiences, embeddings, and metadata from the specified workspace
|
||||
- **Frees up** storage space and computational resources
|
||||
- **Cannot be undone** - ensure you've backed up important data before deletion
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "delete"
|
||||
})
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "delete"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function deleteWorkspace() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "delete"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
deleteWorkspace();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 📋 Copy Workspace
|
||||
|
||||
Duplicate an existing workspace to create a new one with identical experience data, perfect for experimentation or branching. This operation:
|
||||
|
||||
- **Clones** all experiences and embeddings from the source workspace
|
||||
- **Creates** a new independent workspace with the copied data
|
||||
- **Preserves** original workspace while enabling safe testing and modifications in the copy
|
||||
|
||||
<details open>
|
||||
<summary><b>Python</b></summary>
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(url="http://0.0.0.0:8001/vector_store", json={
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "copy",
|
||||
"src_workspace_id": "src_workspace"
|
||||
})
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>curl</b></summary>
|
||||
|
||||
```bash
|
||||
curl -X POST "http://0.0.0.0:8001/vector_store" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workspace_id": "test_workspace",
|
||||
"action": "copy",
|
||||
"src_workspace_id": "src_workspace"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Node.js</b></summary>
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
// or: import fetch from 'node-fetch';
|
||||
|
||||
async function copyWorkspace() {
|
||||
try {
|
||||
const response = await fetch('http://0.0.0.0:8001/vector_store', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspace_id: "test_workspace",
|
||||
action: "copy",
|
||||
src_workspace_id: "src_workspace"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
copyWorkspace();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
🎭 **Want to See It in Action?** We've prepared a [simple react agent](../../cookbook/simple_demo/simple_demo.py) that
|
||||
demonstrates how to enhance agent capabilities by integrating summarizer and retriever components, achieving
|
||||
significantly better performance.
|
||||
|
||||
## 🐛 Common Issues
|
||||
|
||||
### Service Won't Start
|
||||
- Check if port 8001 is available
|
||||
- Verify your API keys in `.env` file
|
||||
- Ensure Python version is 3.12+
|
||||
|
||||
### No Experiences Retrieved
|
||||
- Make sure you've run the summarizer first
|
||||
- Check if workspace_id matches between operations
|
||||
- Verify vector store backend is properly configured
|
||||
|
||||
### API Connection Errors
|
||||
- Confirm LLM_BASE_URL and API keys are correct
|
||||
- Test API access independently
|
||||
- Check network connectivity
|
||||
|
||||
---
|
||||
|
||||
🎯 **You're all set!** You now have a working ExperienceMaker setup that can learn from interactions and improve over time.
|
||||
122
doc/sop_memory/making_sop_memories.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# SOP Memory: Combining Atomic Operations into Complex Workflows
|
||||
|
||||
## 1. Background
|
||||
|
||||
In LLM application development, we often need to combine multiple basic operations (atomic operations) into more complex
|
||||
workflows. These workflows can handle complex tasks such as data retrieval, code generation, multi-turn dialogues, and
|
||||
more. By combining these atomic operations into Standard Operating Procedures (SOPs), we can:
|
||||
|
||||
- Improve code reusability
|
||||
- Simplify implementation of complex tasks
|
||||
- Standardize common workflows
|
||||
- Reduce development and maintenance costs
|
||||
|
||||
This document introduces how to combine atomic operations (Ops) to form new composite operation tools using the FlowLLM
|
||||
framework.
|
||||
|
||||
## 2. Technical Solution
|
||||
|
||||
### 2.1 Atomic Operation Definition
|
||||
|
||||
Each operation (Op) needs to define the following core attributes:
|
||||
|
||||
```python
|
||||
class BaseOp:
|
||||
description: str # Description of the operation
|
||||
input_schema: Dict[str, ParamAttr] # Input parameter schema definition
|
||||
output_schema: Dict[str, ParamAttr] # Output parameter schema definition
|
||||
```
|
||||
|
||||
Where `ParamAttr` defines parameter type, whether it's required, and other attributes:
|
||||
|
||||
```python
|
||||
class ParamAttr:
|
||||
type: Type # Parameter type, such as str, int, Dict, etc.
|
||||
required: bool = True # Whether it must be provided
|
||||
default: Any = None # Default value
|
||||
description: str = "" # Parameter description
|
||||
```
|
||||
|
||||
### 2.2 SOP Composition Process
|
||||
|
||||
#### Step 1: Create Atomic Operation Instances
|
||||
|
||||
First, instantiate the required atomic operations:
|
||||
|
||||
```python
|
||||
from flowllm.op.gallery.mock_op import MockOp
|
||||
from flowllm.op.search.tavily_search_op import TavilySearchOp
|
||||
from flowllm.op.agent.react_v2_op import ReactV2Op
|
||||
|
||||
# Create atomic operation instances
|
||||
search_op = TavilySearchOp()
|
||||
react_op = ReactV2Op()
|
||||
summary_op = MockOp(
|
||||
description="Summarize search results",
|
||||
input_schema={"search_results": ParamAttr(type=str, description="Search results to summarize")},
|
||||
output_schema={"summary": ParamAttr(type=str, description="Summarized content")}
|
||||
)
|
||||
```
|
||||
|
||||
#### Step 2: Define Data Flow Between Operations
|
||||
|
||||
Set up input-output relationships between operations, defining how data flows between them:
|
||||
|
||||
```python
|
||||
# Set input parameter sources
|
||||
react_op.set_input("context",
|
||||
"search_summary") # react_op's context parameter is retrieved from search_summary in memory
|
||||
|
||||
# Set output parameter destinations
|
||||
search_op.set_output("results", "search_results") # search_op's results output to search_results in memory
|
||||
summary_op.set_output("summary", "search_summary") # summary_op's summary output to search_summary in memory
|
||||
```
|
||||
|
||||
#### Step 3: Build Operation Flow Graph
|
||||
|
||||
Use operators to build the operation flow graph, defining execution order and parallel relationships:
|
||||
|
||||
```python
|
||||
# Build operation flow graph
|
||||
flow = search_op >> summary_op >> react_op
|
||||
|
||||
# Or more complex flows
|
||||
# Parallel operations use the | operator, sequential operations use the >> operator
|
||||
complex_flow = (search_op >> summary_op) | (another_search_op >> another_summary_op) >> react_op
|
||||
```
|
||||
|
||||
Operator explanation:
|
||||
|
||||
- `>>`: Sequential execution, execute the next operation after the previous one completes
|
||||
- `|`: Parallel execution, execute multiple operations simultaneously
|
||||
|
||||
#### Step 4: Create Composite Operation Class
|
||||
|
||||
Encapsulate the built operation flow into a new composite operation class:
|
||||
|
||||
```python
|
||||
|
||||
class SearchAndReactOp(BaseToolOp):
|
||||
description = "Search for information and generate a response based on search results"
|
||||
input_schema = ...
|
||||
output_schema = ...
|
||||
|
||||
def build_flow(self):
|
||||
search_op = TavilySearchOp()
|
||||
summary_op = MockOp()
|
||||
react_op = ReactV2Op()
|
||||
|
||||
# Set data flow
|
||||
search_op.set_output("results", "search_results")
|
||||
summary_op.set_input("search_results", "search_results")
|
||||
summary_op.set_output("summary", "search_summary")
|
||||
react_op.set_input("context", "search_summary")
|
||||
react_op.set_output("response", "response")
|
||||
|
||||
# Build operation flow graph
|
||||
return search_op >> summary_op >> react_op
|
||||
|
||||
async def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# Execute operation flow
|
||||
return await self.flow.execute(inputs)
|
||||
```
|
||||
202
doc/task_memory/task_memory.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# Task Memory in Reme
|
||||
|
||||
Task Memory is a key component of Reme that allows AI agents to learn from past experiences and improve their performance on similar tasks in the future. This document explains how task memory works and how to use it in your applications.
|
||||
|
||||
## What is Task Memory?
|
||||
|
||||
Task Memory represents knowledge extracted from previous task executions, including:
|
||||
- Successful approaches to solving problems
|
||||
- Common pitfalls and failures to avoid
|
||||
- Comparative insights between different approaches
|
||||
|
||||
Each task memory contains:
|
||||
- `when_to_use`: Conditions that indicate when this memory is relevant
|
||||
- `content`: The actual knowledge or experience to be applied
|
||||
- Metadata about the memory's source and utility
|
||||
|
||||
## Configuration Logic
|
||||
|
||||
Task Memory in Reme is configured through two main flows:
|
||||
|
||||
### 1. Summary Task Memory
|
||||
|
||||
The `summary_task_memory` flow processes conversation trajectories to extract meaningful memories:
|
||||
|
||||
```yaml
|
||||
summary_task_memory:
|
||||
flow_content: trajectory_preprocess_op >> (success_extraction_op|failure_extraction_op|comparative_extraction_op) >> memory_validation_op >> update_vector_store_op
|
||||
description: "Summarizes conversation trajectories or messages into structured memory representations for long-term storage"
|
||||
```
|
||||
|
||||
This flow:
|
||||
1. Preprocesses trajectories (`trajectory_preprocess_op`)
|
||||
2. Extracts memories based on success/failure/comparative analysis
|
||||
3. Validates memories (`memory_validation_op`)
|
||||
4. Updates the vector store (`update_vector_store_op`)
|
||||
|
||||
A simplified version (`summary_task_memory_simple`) is also available for less complex use cases.
|
||||
|
||||
### 2. Retrieve Task Memory
|
||||
|
||||
The `retrieve_task_memory` flow fetches relevant memories based on a query:
|
||||
|
||||
```yaml
|
||||
retrieve_task_memory:
|
||||
flow_content: build_query_op >> recall_vector_store_op >> rerank_memory_op >> rewrite_memory_op
|
||||
description: "Retrieves the most relevant top-k memory experiences from historical data based on the current query to enhance task-solving capabilities"
|
||||
```
|
||||
|
||||
This flow:
|
||||
1. Builds a query from the input (`build_query_op`)
|
||||
2. Recalls relevant memories from the vector store (`recall_vector_store_op`)
|
||||
3. Reranks memories by relevance (`rerank_memory_op`)
|
||||
4. Rewrites memories for better context integration (`rewrite_memory_op`)
|
||||
|
||||
A simplified version (`retrieve_task_memory_simple`) is also available.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Here's how to use Task Memory in your application:
|
||||
|
||||
### Step 1: Set Up Your Environment
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# API configuration
|
||||
BASE_URL = "http://0.0.0.0:8002/"
|
||||
WORKSPACE_ID = "your_workspace_id"
|
||||
```
|
||||
|
||||
### Step 2: Run an Agent and Generate Memories
|
||||
|
||||
```python
|
||||
# Run the agent with a query
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}react",
|
||||
json={"query": "Your query here"}
|
||||
)
|
||||
messages = response.json().get("messages", [])
|
||||
|
||||
# Summarize the conversation to create task memories
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}summary_task_memory",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Retrieve Relevant Memories for a New Task
|
||||
|
||||
```python
|
||||
# Retrieve memories relevant to a new query
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}retrieve_task_memory",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"query": "Your new query here"
|
||||
}
|
||||
)
|
||||
retrieved_memory = response.json().get("answer", "")
|
||||
```
|
||||
|
||||
### Step 4: Use Retrieved Memories to Enhance Agent Performance
|
||||
|
||||
```python
|
||||
# Augment a new query with retrieved memories
|
||||
augmented_query = f"{retrieved_memory}\n\nUser Question:\n{your_query}"
|
||||
|
||||
# Run agent with the augmented query
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}react",
|
||||
json={"query": augmented_query}
|
||||
)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example workflow that demonstrates how to use task memory:
|
||||
|
||||
```python
|
||||
def run_agent_with_memory(query_first, query_second):
|
||||
# Run agent with second query to build initial memories
|
||||
messages = run_agent(query=query_second)
|
||||
|
||||
# Summarize conversation to create memories
|
||||
requests.post(
|
||||
url=f"{BASE_URL}summary_task_memory",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"trajectories": [
|
||||
{"messages": messages, "score": 1.0}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# Retrieve relevant memories for the first query
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}retrieve_task_memory",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"query": query_first
|
||||
}
|
||||
)
|
||||
retrieved_memory = response.json().get("answer", "")
|
||||
|
||||
# Run agent with first query augmented with retrieved memories
|
||||
augmented_query = f"{retrieved_memory}\n\nUser Question:\n{query_first}"
|
||||
return run_agent(query=augmented_query)
|
||||
```
|
||||
|
||||
## Managing Task Memories
|
||||
|
||||
### Delete a Workspace
|
||||
|
||||
```python
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}vector_store",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "delete"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Dump Memories to Disk
|
||||
|
||||
```python
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}vector_store",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "dump",
|
||||
"path": "./"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Load Memories from Disk
|
||||
|
||||
```python
|
||||
response = requests.post(
|
||||
url=f"{BASE_URL}vector_store",
|
||||
json={
|
||||
"workspace_id": WORKSPACE_ID,
|
||||
"action": "load",
|
||||
"path": "./"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
Reme also provides additional task memory operations:
|
||||
|
||||
- `record_task_memory`: Update frequency and utility attributes of retrieved memories
|
||||
- `delete_task_memory`: Delete memories based on utility/frequency thresholds
|
||||
|
||||
For more detailed examples, see the `use_task_memory_demo.py` file in the cookbook directory of the Reme project.
|
||||
73
doc/task_memory/task_retrieve_ops.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Task Memory Retrieval Operations
|
||||
|
||||
## BuildQueryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Constructs a query for memory retrieval either from a direct query input or by analyzing conversation messages.
|
||||
|
||||
### Functionality
|
||||
|
||||
- If a direct `query` is provided in the context, it uses that query
|
||||
- If `messages` are provided in the context, it can:
|
||||
- Use an LLM to generate a query based on the conversation context
|
||||
- Or create a simple query from recent messages without using an LLM
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.build_query_op.params.enable_llm_build` (boolean, default: `true`):
|
||||
- When `true`, uses an LLM to generate a query from conversation messages
|
||||
- When `false`, creates a simple query by concatenating recent messages
|
||||
|
||||
## RerankMemoryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Reranks and filters recalled memories to ensure the most relevant memories are prioritized.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Reranks memories using LLM-based analysis (optional)
|
||||
- Filters memories based on quality scores (optional)
|
||||
- Returns the top-k most relevant memories
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.rerank_memory_op.params.enable_llm_rerank` (boolean, default: `true`):
|
||||
- When `true`, uses an LLM to rerank memories based on their relevance to the query
|
||||
- `op.rerank_memory_op.params.enable_score_filter` (boolean, default: `false`):
|
||||
- When `true`, filters memories based on their quality scores
|
||||
- `op.rerank_memory_op.params.min_score_threshold` (float, default: `0.3`):
|
||||
- Minimum score threshold for filtering memories when `enable_score_filter` is `true`
|
||||
- `op.rerank_memory_op.params.top_k` (integer, default: `5`):
|
||||
- Number of top memories to retain after reranking
|
||||
|
||||
## RewriteMemoryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Rewrites and formats the retrieved memories to make them more relevant and actionable for the current context.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Formats retrieved memories into a structured format
|
||||
- Can use an LLM to rewrite memories to better fit the current context (optional)
|
||||
- Generates a cohesive context message from multiple memories
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.rewrite_memory_op.params.enable_llm_rewrite` (boolean, default: `true`):
|
||||
- When `true`, uses an LLM to rewrite the memories to make them more relevant and actionable
|
||||
- When `false`, simply formats the memories without LLM-based rewriting
|
||||
|
||||
## MergeMemoryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
An alternative to RewriteMemoryOp that merges multiple memories into a single response without using an LLM.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Collects the content from all memories in the memory list
|
||||
- Formats them into a single response with a standard structure
|
||||
- Adds a prompt to consider the helpful parts when answering the question
|
||||
190
doc/task_memory/task_summary_ops.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Task Summary Operations
|
||||
|
||||
## TrajectoryPreprocessOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Preprocesses trajectories by validating and classifying them based on their score.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Validates and classifies trajectories as success or failure based on a threshold
|
||||
- Modifies tool calls in messages to ensure consistent format
|
||||
- Sets context for downstream operators with classified trajectories
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.trajectory_preprocess_op.params.success_threshold` (float, default: `1.0`):
|
||||
- The threshold score that determines if a trajectory is considered successful
|
||||
- Trajectories with scores greater than or equal to this value are classified as successful
|
||||
|
||||
## TrajectorySegmentationOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Segments trajectories into meaningful step sequences to enable more granular memory extraction.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Uses LLM to identify logical break points in trajectories
|
||||
- Adds segmentation information to trajectory metadata
|
||||
- Enables more focused memory extraction from specific parts of conversations
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.trajectory_segmentation_op.params.segment_target` (string, default: `"all"`):
|
||||
- Determines which trajectories to segment
|
||||
- Options: `"all"`, `"success"`, `"failure"`
|
||||
|
||||
## SuccessExtractionOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Extracts task memories from successful trajectories.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Processes successful trajectories to identify valuable experiences
|
||||
- Can work with both entire trajectories and segmented step sequences
|
||||
- Uses LLM to extract structured task memories with when-to-use conditions
|
||||
|
||||
### Parameters
|
||||
|
||||
No specific parameters beyond the LLM configuration.
|
||||
|
||||
## FailureExtractionOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Extracts task memories from failed trajectories to capture lessons learned from unsuccessful attempts.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Processes failed trajectories to identify pitfalls and mistakes
|
||||
- Can work with both entire trajectories and segmented step sequences
|
||||
- Uses LLM to extract structured task memories with when-to-use conditions
|
||||
|
||||
### Parameters
|
||||
|
||||
No specific parameters beyond the LLM configuration.
|
||||
|
||||
## ComparativeExtractionOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Extracts comparative task memories by comparing different scoring trajectories.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Performs "soft comparison" between highest and lowest scoring trajectories
|
||||
- Can perform "hard comparison" between success and failure trajectories using similarity search
|
||||
- Identifies key differences that contributed to success or failure
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.comparative_extraction_op.params.enable_soft_comparison` (boolean, default: `true`):
|
||||
- When `true`, enables comparison between highest and lowest scoring trajectories
|
||||
- `op.comparative_extraction_op.params.enable_similarity_comparison` (boolean, default: `false`):
|
||||
- When `true`, enables similarity-based comparison between success and failure trajectories
|
||||
- `op.comparative_extraction_op.params.similarity_threshold` (float, default: `0.3`):
|
||||
- The threshold for considering two trajectories similar
|
||||
- `op.comparative_extraction_op.params.max_similarity_sequences` (integer, default: `5`):
|
||||
- Maximum number of sequences to compare to avoid computational overload
|
||||
- `op.comparative_extraction_op.params.max_similarity_pairs` (integer, default: `3`):
|
||||
- Maximum number of similar pairs to process
|
||||
|
||||
## MemoryValidationOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Validates the quality of extracted task memories to ensure they are useful and relevant.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Uses LLM to validate each extracted memory
|
||||
- Scores memories based on quality and relevance
|
||||
- Filters out low-quality memories based on validation threshold
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.memory_validation_op.params.validation_threshold` (float, default: `0.5`):
|
||||
- The minimum score for a memory to be considered valid
|
||||
|
||||
## MemoryDeduplicationOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Removes duplicate task memories to avoid redundancy in the vector store.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Compares new memories with existing memories in the vector store
|
||||
- Uses embedding similarity to identify duplicates
|
||||
- Ensures only unique memories are stored
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.memory_deduplication_op.params.similarity_threshold` (float, default: `0.5`):
|
||||
- The threshold for considering two memories similar
|
||||
- `op.memory_deduplication_op.params.max_existing_task_memories` (integer, default: `1000`):
|
||||
- Maximum number of existing memories to check against
|
||||
|
||||
## SimpleSummaryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
A simplified version of memory extraction that processes entire trajectories in one step.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Classifies trajectories as success or failure based on score threshold
|
||||
- Extracts memories directly from complete trajectories
|
||||
- Useful for simpler use cases where detailed segmentation is not required
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.simple_summary_op.params.success_score_threshold` (float, default: `0.9`):
|
||||
- The threshold score that determines if a trajectory is considered successful
|
||||
|
||||
## SimpleComparativeSummaryOp
|
||||
|
||||
### Purpose
|
||||
|
||||
A simplified version of comparative memory extraction.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Groups trajectories by task ID
|
||||
- Compares the highest and lowest scoring trajectories for each task
|
||||
- Extracts comparative insights without complex segmentation
|
||||
|
||||
### Parameters
|
||||
|
||||
No specific parameters beyond the LLM configuration.
|
||||
|
||||
## PDFPreprocessOp
|
||||
|
||||
### Purpose
|
||||
|
||||
Processes PDF files to extract content that can be used for memory creation.
|
||||
|
||||
### Functionality
|
||||
|
||||
- Extracts text content from PDF files
|
||||
- Creates markdown representation of PDF content
|
||||
- Chunks content into manageable pieces for processing
|
||||
|
||||
### Parameters
|
||||
|
||||
- `op.pdf_preprocess_op.params.method` (string, default: `"auto"`):
|
||||
- The method to use for PDF processing
|
||||
- Options: `"auto"`, `"text"`, `"layout"`
|
||||
- `op.pdf_preprocess_op.params.lang` (string, default: `null` (auto-detect)):
|
||||
- The language of the PDF content
|
||||
- `op.pdf_preprocess_op.params.backend` (string, default: `"pipeline"`):
|
||||
- The backend to use for PDF processing
|
||||
- Options: `"pipeline"`, `"pdfminer"`
|
||||
- `op.pdf_preprocess_op.params.create_chunks` (boolean, default: `true`):
|
||||
- Whether to create chunks from the PDF content
|
||||
- `op.pdf_preprocess_op.params.max_chunk_length` (integer, default: `4000`):
|
||||
- The maximum length of each chunk
|
||||
390
doc/vector_store_api_guide.md
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
# 🚀 Vector Store API Guide
|
||||
|
||||
This guide covers the vector store implementations available in flowllm, their APIs, and how to use them effectively.
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
flowllm provides multiple vector store backends for different use cases:
|
||||
|
||||
- **LocalVectorStore** (`backend=local`) - 📁 Simple file-based storage for development and small datasets
|
||||
- **ChromaVectorStore** (`backend=chroma`) - 🔮 Embedded vector database for moderate scale
|
||||
- **EsVectorStore** (`backend=elasticsearch`) - 🔍 Elasticsearch-based storage for production and large scale
|
||||
|
||||
All vector stores implement the `BaseVectorStore` interface, providing a consistent API across implementations.
|
||||
|
||||
## 🔄 Common API Methods
|
||||
|
||||
All vector store implementations share these core methods:
|
||||
|
||||
### Workspace Management
|
||||
|
||||
```python
|
||||
# Check if workspace exists
|
||||
store.exist_workspace(workspace_id: str) -> bool
|
||||
|
||||
# Create a new workspace
|
||||
store.create_workspace(workspace_id: str, **kwargs)
|
||||
|
||||
# Delete a workspace
|
||||
store.delete_workspace(workspace_id: str, **kwargs)
|
||||
|
||||
# Copy a workspace
|
||||
store.copy_workspace(src_workspace_id: str, dest_workspace_id: str, **kwargs)
|
||||
```
|
||||
|
||||
### Data Operations
|
||||
|
||||
```python
|
||||
# Insert nodes (single or list)
|
||||
store.insert(nodes: VectorNode | List[VectorNode], workspace_id: str, **kwargs)
|
||||
|
||||
# Delete nodes by ID
|
||||
store.delete(node_ids: str | List[str], workspace_id: str, **kwargs)
|
||||
|
||||
# Search for similar nodes
|
||||
store.search(query: str, workspace_id: str, top_k: int = 1, **kwargs) -> List[VectorNode]
|
||||
|
||||
# Iterate through workspace nodes
|
||||
for node in store.iter_workspace_nodes(workspace_id: str, **kwargs):
|
||||
# Process each node
|
||||
```
|
||||
|
||||
### Import/Export
|
||||
|
||||
```python
|
||||
# Export workspace to file
|
||||
store.dump_workspace(workspace_id: str, path: str | Path = "", callback_fn=None, **kwargs)
|
||||
|
||||
# Import workspace from file
|
||||
store.load_workspace(workspace_id: str, path: str | Path = "", nodes: List[VectorNode] = None,
|
||||
callback_fn=None, **kwargs)
|
||||
```
|
||||
|
||||
## ⚡ Vector Store Implementations
|
||||
|
||||
### 1. 📁 LocalVectorStore (`backend=local`)
|
||||
|
||||
A simple file-based vector store that saves data to local JSONL files.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Development and testing** - No external dependencies required 🛠️
|
||||
- **Small datasets** - Suitable for datasets with < 10,000 vectors 📊
|
||||
- **Single-user applications** - Limited concurrent access support 👤
|
||||
|
||||
#### ⚙️ Configuration
|
||||
|
||||
```python
|
||||
from flowllm.storage.vector_store import LocalVectorStore
|
||||
from flowllm.embedding_model import OpenAICompatibleEmbeddingModel
|
||||
from flowllm.utils.common_utils import load_env
|
||||
|
||||
# Load environment variables (for API keys)
|
||||
load_env()
|
||||
|
||||
# Initialize embedding model
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
# Initialize vector store
|
||||
vector_store = LocalVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
store_dir="./file_vector_store", # Directory to store JSONL files
|
||||
batch_size=1024 # Batch size for operations
|
||||
)
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
|
||||
```python
|
||||
from flowllm.schema.vector_node import VectorNode
|
||||
|
||||
# Create workspace
|
||||
workspace_id = "my_workspace"
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
# Create nodes
|
||||
nodes = [
|
||||
VectorNode(
|
||||
unique_id="node1",
|
||||
workspace_id=workspace_id,
|
||||
content="Artificial intelligence is revolutionizing technology",
|
||||
metadata={"category": "tech", "source": "article1"}
|
||||
),
|
||||
VectorNode(
|
||||
unique_id="node2",
|
||||
workspace_id=workspace_id,
|
||||
content="Machine learning enables data-driven insights",
|
||||
metadata={"category": "tech", "source": "article2"}
|
||||
)
|
||||
]
|
||||
|
||||
# Insert nodes
|
||||
vector_store.insert(nodes, workspace_id)
|
||||
|
||||
# Search
|
||||
results = vector_store.search("What is AI?", workspace_id, top_k=2)
|
||||
for result in results:
|
||||
print(f"Content: {result.content}")
|
||||
print(f"Metadata: {result.metadata}")
|
||||
print(f"Score: {result.metadata.get('score', 'N/A')}")
|
||||
```
|
||||
|
||||
### 2. 🔮 ChromaVectorStore (`backend=chroma`)
|
||||
|
||||
An embedded vector database that provides persistent storage with advanced features.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Local development** with persistence requirements 🏠
|
||||
- **Medium-scale applications** (10K - 1M vectors) 📈
|
||||
- **Applications requiring metadata filtering** 🔍
|
||||
|
||||
#### ⚙️ Configuration
|
||||
|
||||
```python
|
||||
from flowllm.storage.vector_store import ChromaVectorStore
|
||||
from flowllm.embedding_model import OpenAICompatibleEmbeddingModel
|
||||
from flowllm.utils.common_utils import load_env
|
||||
|
||||
# Load environment variables
|
||||
load_env()
|
||||
|
||||
# Initialize embedding model
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
# Initialize vector store
|
||||
vector_store = ChromaVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
store_dir="./chroma_vector_store", # Directory for Chroma database
|
||||
batch_size=1024 # Batch size for operations
|
||||
)
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
|
||||
```python
|
||||
from flowllm.schema.vector_node import VectorNode
|
||||
|
||||
workspace_id = "chroma_workspace"
|
||||
|
||||
# Check if workspace exists and create if needed
|
||||
if not vector_store.exist_workspace(workspace_id):
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
# Create nodes with metadata
|
||||
nodes = [
|
||||
VectorNode(
|
||||
unique_id="node1",
|
||||
workspace_id=workspace_id,
|
||||
content="Deep learning models require large datasets",
|
||||
metadata={
|
||||
"category": "AI",
|
||||
"difficulty": "advanced",
|
||||
"topic": "deep_learning"
|
||||
}
|
||||
),
|
||||
VectorNode(
|
||||
unique_id="node2",
|
||||
workspace_id=workspace_id,
|
||||
content="Transformer architecture revolutionized NLP",
|
||||
metadata={
|
||||
"category": "AI",
|
||||
"difficulty": "intermediate",
|
||||
"topic": "transformers"
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
# Insert nodes
|
||||
vector_store.insert(nodes, workspace_id)
|
||||
|
||||
# Search
|
||||
results = vector_store.search("deep learning", workspace_id, top_k=5)
|
||||
for result in results:
|
||||
print(f"Content: {result.content}")
|
||||
print(f"Metadata: {result.metadata}")
|
||||
```
|
||||
|
||||
### 3. 🔍 EsVectorStore (`backend=elasticsearch`)
|
||||
|
||||
Production-grade vector search using Elasticsearch with advanced filtering and scaling capabilities.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Production environments** requiring high availability 🏭
|
||||
- **Large-scale applications** (1M+ vectors) 🚀
|
||||
- **Complex filtering requirements** on metadata 🎯
|
||||
|
||||
#### 🛠️ Setup Elasticsearch
|
||||
|
||||
Before using EsVectorStore, set up Elasticsearch:
|
||||
|
||||
##### Option 1: Docker Run
|
||||
```bash
|
||||
# Pull the latest Elasticsearch image
|
||||
docker pull docker.elastic.co/elasticsearch/elasticsearch-wolfi:9.0.0
|
||||
|
||||
# Run Elasticsearch container
|
||||
docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
-e "http.host=0.0.0.0" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch-wolfi:9.0.0
|
||||
```
|
||||
|
||||
##### Environment Configuration
|
||||
```bash
|
||||
export FLOW_ES_HOSTS=http://localhost:9200
|
||||
```
|
||||
|
||||
#### ⚙️ Configuration
|
||||
|
||||
```python
|
||||
from flowllm.storage.vector_store import EsVectorStore
|
||||
from flowllm.embedding_model import OpenAICompatibleEmbeddingModel
|
||||
from flowllm.utils.common_utils import load_env
|
||||
import os
|
||||
|
||||
# Load environment variables
|
||||
load_env()
|
||||
|
||||
# Initialize embedding model
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
# Initialize vector store
|
||||
vector_store = EsVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
hosts=os.getenv("FLOW_ES_HOSTS", "http://localhost:9200"), # Elasticsearch hosts
|
||||
basic_auth=None, # ("username", "password") for auth
|
||||
batch_size=1024 # Batch size for bulk operations
|
||||
)
|
||||
```
|
||||
|
||||
#### 🎯 Advanced Filtering
|
||||
|
||||
EsVectorStore supports advanced filtering capabilities:
|
||||
|
||||
```python
|
||||
# Add term filters
|
||||
vector_store.add_term_filter("metadata.category", "technology")
|
||||
|
||||
# Add range filters
|
||||
vector_store.add_range_filter("metadata.score", gte=0.8)
|
||||
vector_store.add_range_filter("metadata.timestamp", gte="2024-01-01", lte="2024-12-31")
|
||||
|
||||
# Search with filters applied
|
||||
results = vector_store.search("machine learning", workspace_id, top_k=10)
|
||||
|
||||
# Clear filters for next search
|
||||
vector_store.clear_filter()
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
|
||||
```python
|
||||
from flowllm.schema.vector_node import VectorNode
|
||||
|
||||
# Define workspace
|
||||
workspace_id = "production_workspace"
|
||||
|
||||
# Create workspace if needed
|
||||
if not vector_store.exist_workspace(workspace_id):
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
# Create nodes with rich metadata
|
||||
nodes = [
|
||||
VectorNode(
|
||||
unique_id="doc1",
|
||||
workspace_id=workspace_id,
|
||||
content="Transformer architecture revolutionized NLP",
|
||||
metadata={
|
||||
"category": "AI",
|
||||
"subcategory": "NLP",
|
||||
"author": "research_team",
|
||||
"timestamp": "2024-01-15",
|
||||
"confidence": 0.95,
|
||||
"tags": ["transformer", "nlp", "attention"]
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
# Insert with refresh for immediate availability
|
||||
vector_store.insert(nodes, workspace_id, refresh=True)
|
||||
|
||||
# Advanced search with filters
|
||||
vector_store.add_term_filter("metadata.category", "AI")
|
||||
vector_store.add_range_filter("metadata.confidence", gte=0.9)
|
||||
|
||||
results = vector_store.search("transformer models", workspace_id, top_k=5)
|
||||
|
||||
for result in results:
|
||||
print(f"Score: {result.metadata.get('score', 'N/A')}")
|
||||
print(f"Content: {result.content}")
|
||||
print(f"Metadata: {result.metadata}")
|
||||
```
|
||||
|
||||
## 📝 Working with VectorNode
|
||||
|
||||
The `VectorNode` class is the fundamental data unit for all vector stores:
|
||||
|
||||
```python
|
||||
from flowllm.schema.vector_node import VectorNode
|
||||
|
||||
# Create a node
|
||||
node = VectorNode(
|
||||
unique_id="unique_identifier", # Unique ID for the node (required)
|
||||
workspace_id="my_workspace", # Workspace ID (required)
|
||||
content="Text content to embed", # Content to be embedded (required)
|
||||
metadata={ # Optional metadata
|
||||
"source": "document1",
|
||||
"category": "technology",
|
||||
"timestamp": "2024-08-29"
|
||||
},
|
||||
vector=None # Vector will be generated automatically if None
|
||||
)
|
||||
```
|
||||
|
||||
## 🔄 Import/Export Example
|
||||
|
||||
Export and import workspaces for backup or transfer:
|
||||
|
||||
```python
|
||||
# Export workspace to file
|
||||
vector_store.dump_workspace(
|
||||
workspace_id="my_workspace",
|
||||
path="./backup_data" # Directory to store the exported data
|
||||
)
|
||||
|
||||
# Import workspace from file
|
||||
vector_store.load_workspace(
|
||||
workspace_id="new_workspace",
|
||||
path="./backup_data" # Directory containing the exported data
|
||||
)
|
||||
|
||||
# Copy workspace within the same store
|
||||
vector_store.copy_workspace(
|
||||
src_workspace_id="original_workspace",
|
||||
dest_workspace_id="copied_workspace"
|
||||
)
|
||||
```
|
||||
|
||||
## 🧩 Integration with Embedding Models
|
||||
|
||||
All vector stores require an embedding model to function:
|
||||
|
||||
```python
|
||||
from flowllm.embedding_model import OpenAICompatibleEmbeddingModel
|
||||
|
||||
# Initialize embedding model
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(
|
||||
dimensions=1024, # Embedding dimensions
|
||||
model_name="text-embedding-v4", # Model name
|
||||
batch_size=32 # Batch size for embedding generation
|
||||
)
|
||||
|
||||
# Pass to vector store
|
||||
vector_store = LocalVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
store_dir="./vector_store"
|
||||
)
|
||||
```
|
||||
|
||||
🎉 This guide provides everything you need to work with vector stores in flowllm. Choose the implementation that best fits your use case and scale up as needed! ✨
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
# 🚀 Vector Store Quick Start Guide
|
||||
This comprehensive guide covers all available vector store implementations in ExperienceMaker, their differences, use cases, and setup instructions.
|
||||
|
||||
## 📋 Overview
|
||||
ExperienceMaker supports multiple vector store backends for different use cases and deployment scenarios:
|
||||
- **FileVectorStore** (`backend=local_file`) - 📁 Local file-based storage for development and small datasets
|
||||
- **ChromaVectorStore** (`backend=chroma`) - 🔮 Embedded vector database for local development and moderate scale
|
||||
- **EsVectorStore** (`backend=elasticsearch`) - 🔍 Elasticsearch-based storage for production and large scale
|
||||
|
||||
## ⚡ Vector Store Implementations
|
||||
### 1. 📁 FileVectorStore (`backend=local_file`)
|
||||
A simple file-based vector store that saves data to local JSONL files. Perfect for development, testing, and small datasets.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Development and testing** - No external dependencies required 🛠️
|
||||
- **Small datasets** - Suitable for datasets with < 10,000 vectors 📊
|
||||
- **Single-user applications** - No concurrent access support 👤
|
||||
- **Prototyping** - Quick setup without infrastructure ⚡
|
||||
|
||||
#### ✨ Features
|
||||
- ✅ No external dependencies
|
||||
- ✅ Simple file-based persistence
|
||||
- ✅ Built-in cosine similarity search
|
||||
- ❌ No concurrent access support
|
||||
- ❌ Limited scalability
|
||||
- ❌ No advanced filtering
|
||||
|
||||
#### ⚙️ Configuration Parameters
|
||||
```python
|
||||
from experiencemaker.vector_store import FileVectorStore
|
||||
from experiencemaker.embedding_model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
|
||||
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
vector_store = FileVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
store_dir="./file_vector_store", # Directory to store JSONL files
|
||||
batch_size=1024 # Batch size for operations
|
||||
)
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
```python
|
||||
# Create workspace and insert data
|
||||
workspace_id = "my_workspace"
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
nodes = [
|
||||
VectorNode(
|
||||
workspace_id=workspace_id,
|
||||
content="Artificial intelligence is revolutionizing technology",
|
||||
metadata={"category": "tech", "source": "article1"}
|
||||
),
|
||||
VectorNode(
|
||||
workspace_id=workspace_id,
|
||||
content="Machine learning enables data-driven insights",
|
||||
metadata={"category": "tech", "source": "article2"}
|
||||
)
|
||||
]
|
||||
|
||||
vector_store.insert(nodes, workspace_id)
|
||||
|
||||
# Search
|
||||
results = vector_store.search("What is AI?", workspace_id, top_k=2)
|
||||
```
|
||||
|
||||
### 2. 🔮 ChromaVectorStore (`backend=chroma`)
|
||||
|
||||
An embedded vector database that provides persistent storage with advanced features while remaining easy to deploy.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Local development** with persistence requirements 🏠
|
||||
- **Medium-scale applications** (10K - 1M vectors) 📈
|
||||
- **Multi-user applications** with moderate concurrency 👥
|
||||
- **Applications requiring metadata filtering** 🔍
|
||||
- **Docker deployments** without external database dependencies 🐳
|
||||
|
||||
#### ✨ Features
|
||||
- ✅ Persistent embedded database
|
||||
- ✅ Advanced metadata filtering
|
||||
- ✅ Built-in vector indexing (HNSW)
|
||||
- ✅ HTTP API support
|
||||
- ✅ Concurrent access support
|
||||
- ✅ Collection management
|
||||
- ❌ Limited horizontal scaling
|
||||
|
||||
#### ⚙️ Configuration Parameters
|
||||
```python
|
||||
from experiencemaker.vector_store import ChromaVectorStore
|
||||
from experiencemaker.embedding_model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
|
||||
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
vector_store = ChromaVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
store_dir="./chroma_vector_store", # Directory for Chroma database
|
||||
batch_size=1024 # Batch size for operations
|
||||
)
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
```python
|
||||
workspace_id = "chroma_workspace"
|
||||
|
||||
# Check if workspace exists
|
||||
if not vector_store.exist_workspace(workspace_id):
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
# Insert with metadata
|
||||
nodes = [
|
||||
VectorNode(
|
||||
workspace_id=workspace_id,
|
||||
content="Deep learning models require large datasets",
|
||||
metadata={"category": "AI", "difficulty": "advanced", "topic": "deep_learning"}
|
||||
)
|
||||
]
|
||||
|
||||
vector_store.insert(nodes, workspace_id)
|
||||
|
||||
# Search with results
|
||||
results = vector_store.search("deep learning", workspace_id, top_k=5)
|
||||
for result in results:
|
||||
print(f"Content: {result.content}")
|
||||
print(f"Metadata: {result.metadata}")
|
||||
```
|
||||
|
||||
### 3. 🔍 EsVectorStore (`backend=elasticsearch`)
|
||||
|
||||
Production-grade vector search using Elasticsearch with advanced filtering, scaling, and enterprise features.
|
||||
|
||||
#### 💡 When to Use
|
||||
- **Production environments** requiring high availability 🏭
|
||||
- **Large-scale applications** (1M+ vectors) 🚀
|
||||
- **High-throughput scenarios** with many concurrent users ⚡
|
||||
- **Complex filtering requirements** on metadata 🎯
|
||||
- **Distributed deployments** across multiple nodes 🌐
|
||||
- **Enterprise environments** with existing Elasticsearch infrastructure 🏢
|
||||
|
||||
#### ✨ Features
|
||||
- ✅ Horizontal scaling
|
||||
- ✅ High availability and fault tolerance
|
||||
- ✅ Advanced filtering and aggregations
|
||||
- ✅ Real-time indexing and search
|
||||
- ✅ Cluster management
|
||||
- ✅ Enterprise security features
|
||||
- ✅ Monitoring and analytics
|
||||
- ❌ Complex setup and maintenance
|
||||
- ❌ Higher resource requirements
|
||||
|
||||
#### 🛠️ Setup Elasticsearch
|
||||
|
||||
Before using EsVectorStore, you need to set up Elasticsearch. Choose one of the following methods:
|
||||
|
||||
##### Option 1: All-in-One Script (Recommended for Development) 🎯
|
||||
```bash
|
||||
curl -fsSL https://elastic.co/start-local | sh
|
||||
```
|
||||
|
||||
##### Option 2: Docker Run with HTTP Host 🐳
|
||||
```bash
|
||||
# Pull the latest Elasticsearch image
|
||||
docker pull docker.elastic.co/elasticsearch/elasticsearch-wolfi:9.0.0
|
||||
|
||||
# Run Elasticsearch container
|
||||
docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
-e "http.host=0.0.0.0" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch-wolfi:9.0.0
|
||||
```
|
||||
|
||||
##### 🔧 Environment Configuration
|
||||
Set the Elasticsearch hosts environment variable:
|
||||
```bash
|
||||
export ES_HOSTS=http://localhost:9200
|
||||
```
|
||||
|
||||
#### ⚙️ Configuration Parameters
|
||||
```python
|
||||
from experiencemaker.vector_store import EsVectorStore
|
||||
from experiencemaker.embedding_model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
|
||||
import os
|
||||
|
||||
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=1024, model_name="text-embedding-v4")
|
||||
|
||||
vector_store = EsVectorStore(
|
||||
embedding_model=embedding_model,
|
||||
hosts=os.getenv("ES_HOSTS", "http://localhost:9200"), # Elasticsearch hosts
|
||||
basic_auth=None, # ("username", "password") for auth
|
||||
batch_size=1024, # Batch size for bulk operations
|
||||
retrieve_filters=[] # Pre-configured filters
|
||||
)
|
||||
```
|
||||
|
||||
#### 🎯 Advanced Filtering
|
||||
EsVectorStore supports advanced filtering capabilities:
|
||||
|
||||
```python
|
||||
# Add term filters
|
||||
vector_store.add_term_filter("metadata.category", "technology")
|
||||
vector_store.add_term_filter("metadata.language", "en")
|
||||
|
||||
# Add range filters
|
||||
vector_store.add_range_filter("metadata.score", gte=0.8)
|
||||
vector_store.add_range_filter("metadata.timestamp", gte="2024-01-01", lte="2024-12-31")
|
||||
|
||||
# Search with filters applied
|
||||
results = vector_store.search("machine learning", workspace_id, top_k=10)
|
||||
|
||||
# Clear filters for next search
|
||||
vector_store.clear_filter()
|
||||
```
|
||||
|
||||
#### 💻 Example Usage
|
||||
```python
|
||||
from experiencemaker.schema.vector_node import VectorNode
|
||||
|
||||
# Configure connection
|
||||
workspace_id = "production_workspace"
|
||||
|
||||
# Create workspace with custom mapping
|
||||
if not vector_store.exist_workspace(workspace_id):
|
||||
vector_store.create_workspace(workspace_id)
|
||||
|
||||
# Insert with rich metadata
|
||||
nodes = [
|
||||
VectorNode(
|
||||
workspace_id=workspace_id,
|
||||
content="Transformer architecture revolutionized NLP",
|
||||
metadata={
|
||||
"category": "AI",
|
||||
"subcategory": "NLP",
|
||||
"author": "research_team",
|
||||
"timestamp": "2024-01-15",
|
||||
"confidence": 0.95,
|
||||
"tags": ["transformer", "nlp", "attention"]
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
# Insert with refresh for immediate availability
|
||||
vector_store.insert(nodes, workspace_id, refresh=True)
|
||||
|
||||
# Advanced search with filters
|
||||
vector_store.add_term_filter("metadata.category", "AI")
|
||||
vector_store.add_range_filter("metadata.confidence", gte=0.9)
|
||||
|
||||
results = vector_store.search("transformer models", workspace_id, top_k=5)
|
||||
|
||||
for result in results:
|
||||
print(f"Score: {result.metadata.get('_score', 'N/A')}")
|
||||
print(f"Content: {result.content}")
|
||||
print(f"Metadata: {result.metadata}")
|
||||
```
|
||||
|
||||
🎉 This guide provides everything you need to get started with vector stores in ExperienceMaker. Choose the implementation that best fits your use case and scale up as needed! ✨
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
# __ __ ____
|
||||
# | \/ | ___ _ __ ___ ___ _ __ _ _/ ___| ___ ___ _ __ ___
|
||||
# | |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | \___ \ / __/ _ \| '_ \ / _ \
|
||||
# | | | | __/ | | | | | (_) | | | |_| |___) | (_| (_) | |_) | __/
|
||||
# |_| |_|\___|_| |_| |_|\___/|_| \__, |____/ \___\___/| .__/ \___|
|
||||
# |___/ |_|
|
||||
|
||||
# Instruction
|
||||
|
||||
# To construct docker image:
|
||||
# sudo docker build --network=host -t memoryscope .
|
||||
|
||||
# To run docker image:
|
||||
# sudo docker run -it --rm --memory=4G --net=host memoryscope
|
||||
# To run docker image with arguments (refer to memoryscope/core/config/arguments.py):
|
||||
# sudo docker run -it --rm --memory=4G --net=host -e "OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -e "language=en" -e "human_name=superman" -e "generation_backend=openai_generation" -e "generation_model=gpt-4o" -e "embedding_backend=openai_embedding" -e "embedding_model=text-embedding-3-small" -e "enable_ranker=False" memoryscope
|
||||
|
||||
FROM python:3.11
|
||||
|
||||
# (Not necessary) Change pip source
|
||||
RUN echo '[global]' > /etc/pip.conf && \
|
||||
echo 'index-url = https://mirrors.aliyun.com/pypi/simple/' >> /etc/pip.conf && \
|
||||
echo 'trusted-host = mirrors.aliyun.com' >> /etc/pip.conf
|
||||
|
||||
# Install Elastic Search
|
||||
RUN useradd -m elastic_search_user
|
||||
USER elastic_search_user
|
||||
WORKDIR /home/elastic_search_user/elastic_search
|
||||
# COPY elasticsearch-8.15.0-linux-x86_64.tar.gz ./elasticsearch-8.15.0-linux-x86_64.tar.gz
|
||||
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.15.0-linux-x86_64.tar.gz
|
||||
RUN tar -xzf elasticsearch-8.15.0-linux-x86_64.tar.gz
|
||||
WORKDIR /home/elastic_search_user/elastic_search/elasticsearch-8.15.0
|
||||
ENV DISCOVERY_TYPE=single-node \
|
||||
XPACK_SECURITY_ENABLED=false \
|
||||
XPACK_LICENSE_SELF_GENERATED_TYPE=trial
|
||||
|
||||
# Change user back to root and fix ownership
|
||||
USER root
|
||||
RUN chown -R elastic_search_user:elastic_search_user /home/elastic_search_user/
|
||||
WORKDIR /memory_scope_project
|
||||
|
||||
# (Not necessary) Install the majority of deps, using docker build cache to accelerate future building
|
||||
COPY requirements.txt ./
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Enter working dir
|
||||
WORKDIR /memory_scope_project
|
||||
COPY . .
|
||||
# RUN pip3 install poetry
|
||||
# RUN poetry install
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Launch!
|
||||
# CMD ["bash"]
|
||||
CMD ["bash", "examples/docker/entrypoint.sh"]
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# __ __ ____
|
||||
# | \/ | ___ _ __ ___ ___ _ __ _ _/ ___| ___ ___ _ __ ___
|
||||
# | |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | \___ \ / __/ _ \| '_ \ / _ \
|
||||
# | | | | __/ | | | | | (_) | | | |_| |___) | (_| (_) | |_) | __/
|
||||
# |_| |_|\___|_| |_| |_|\___/|_| \__, |____/ \___\___/| .__/ \___|
|
||||
# |___/ |_|
|
||||
|
||||
# Instruction
|
||||
|
||||
# To construct docker image:
|
||||
# sudo docker build --network=host -t memoryscope .
|
||||
|
||||
# To run docker image:
|
||||
# sudo docker run -it --rm --memory=4G --net=host memoryscope
|
||||
# To run docker image with arguments (refer to memoryscope/core/config/arguments.py):
|
||||
# sudo docker run -it --rm --memory=4G --net=host -e "OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -e "language=en" -e "human_name=superman" -e "generation_backend=openai_generation" -e "generation_model=gpt-4o" -e "embedding_backend=openai_embedding" -e "embedding_model=text-embedding-3-small" -e "enable_ranker=False" memoryscope
|
||||
#docker run -it --rm ghcr.io/modelscope/memoryscope_arm /bin/bash
|
||||
FROM python:3.11
|
||||
|
||||
# (Not necessary) Change pip source
|
||||
RUN echo '[global]' > /etc/pip.conf && \
|
||||
echo 'index-url = https://mirrors.aliyun.com/pypi/simple/' >> /etc/pip.conf && \
|
||||
echo 'trusted-host = mirrors.aliyun.com' >> /etc/pip.conf
|
||||
|
||||
# Install Elastic Search
|
||||
RUN useradd -m elastic_search_user
|
||||
USER elastic_search_user
|
||||
WORKDIR /home/elastic_search_user/elastic_search
|
||||
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.15.2-linux-aarch64.tar.gz
|
||||
RUN tar -xzf elasticsearch-8.15.2-linux-aarch64.tar.gz
|
||||
RUN mv /home/elastic_search_user/elastic_search/elasticsearch-8.15.2 /home/elastic_search_user/elastic_search/elasticsearch-8.15.0
|
||||
WORKDIR /home/elastic_search_user/elastic_search/elasticsearch-8.15.0
|
||||
ENV DISCOVERY_TYPE=single-node \
|
||||
XPACK_SECURITY_ENABLED=false \
|
||||
XPACK_LICENSE_SELF_GENERATED_TYPE=trial
|
||||
|
||||
# Change user back to root and fix ownership
|
||||
USER root
|
||||
RUN chown -R elastic_search_user:elastic_search_user /home/elastic_search_user/
|
||||
WORKDIR /memory_scope_project
|
||||
|
||||
# (Not necessary) Install the majority of deps, using docker build cache to accelerate future building
|
||||
COPY requirements.txt ./
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Enter working dir
|
||||
WORKDIR /memory_scope_project
|
||||
COPY . .
|
||||
# RUN pip3 install poetry
|
||||
# RUN poetry install
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
# Launch!
|
||||
# CMD ["bash"]
|
||||
CMD ["bash", "examples/docker/entrypoint.sh"]
|
||||
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
English | [**中文**](./README_ZH.md) | [**日本語**](./README_JP.md)
|
||||
|
||||
# MemoryScope
|
||||
<p align="center">
|
||||
<img src="./docs/images/logo.png" alt="MemoryScopeLogo" width="75%">
|
||||
</p>
|
||||
Equip your LLM chatbot with a powerful and flexible long term memory system.
|
||||
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](./LICENSE)
|
||||
[](https://modelscope.github.io/MemoryScope/en/index.html#welcome-to-memoryscope-tutorial)
|
||||
[](https://modelscope.github.io/MemoryScope/en/docs/api.html)
|
||||
[](https://modelscope.github.io/MemoryScope/en/docs/contribution.html)
|
||||
|
||||
----
|
||||
## 📰 News
|
||||
|
||||
- **[2024-09-10]** We release MemoryScope v0.1.1.0 now, which is also available in [PyPI](https://pypi.org/simple/memoryscope/)!
|
||||
----
|
||||
## 🌟 What is MemoryScope?
|
||||
MemoryScope provides LLM chatbots with powerful and flexible long-term memory capabilities, offering a framework for building such abilities.
|
||||
It can be applied to scenarios like personal assistants and emotional companions, continuously learning through long-term memory to remember users' basic information as well as various habits and preferences.
|
||||
This allows users to gradually experience a sense of "understanding" when using the LLM.
|
||||
|
||||
### Demo
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/1754c814-1342-4288-a8a3-74d0b40f59a6" alt="en_demo" width="75%">
|
||||
</p>
|
||||
|
||||
### Framework
|
||||
<p align="center">
|
||||
<img src="./docs/images/framework.png" alt="Framework" width="75%">
|
||||
</p>
|
||||
|
||||
💾 Memory Database: MemoryScope is equipped with a vector database (default is *ElasticSearch*) to store all memory fragments recorded in the system.
|
||||
|
||||
🔧 Worker Library: MemoryScope atomizes the capabilities of long-term memory into individual workers, including over 20 workers for tasks such as query information filtering, observation extraction, and insight updating.
|
||||
|
||||
🛠️ Operation Library: Based on the worker pipeline, it constructs the operations for memory services, realizing key capabilities such as memory retrieval and memory consolidation.
|
||||
|
||||
- Memory Retrieval: Upon arrival of a user query, this operation returns the semantically related memory pieces
|
||||
and/or those from the corresponding time if the query involves reference to time.
|
||||
- Memory Consolidation: This operation takes in a batch of user queries and returns important user information
|
||||
extracted from the queries as consolidated *observations* to be stored in the memory database.
|
||||
- Reflection and Re-consolidation: At regular intervals, this operation performs reflection upon newly recorded *observations*
|
||||
to form and update *insights*. Then, memory re-consolidation is performed to ensure contradictions and repetitions
|
||||
among memory pieces are properly handled.
|
||||
|
||||
|
||||
⚙️ Best Practices:
|
||||
|
||||
- Based on the core capabilities of long-term memory, MemoryScope has implemented a dialogue interface (API) with long-term memory and a command-line dialogue practice (CLI) with long-term memory.
|
||||
- MemoryScope combines currently popular agent frameworks (AutoGen, AgentScope) to provide best practices.
|
||||
|
||||
### Main Features
|
||||
|
||||
⚡ Low response-time (RT) for the user:
|
||||
- Backend operations (Memory Consolidation, Reflection and Re-consolidation) are decoupled from the frontend operation
|
||||
(Memory Retrieval) in the system.
|
||||
- While backend operations are usually (and are recommended to be) queued or executed at regular intervals, the
|
||||
system's response time (RT) for the user depends solely on the frontend operation, which is only ~500ms.
|
||||
|
||||
🌲 Hierarchical and coherent memory:
|
||||
- The memory pieces stored in the system are in a hierarchical structure, with *insights* being the high level information
|
||||
from the aggregation of similarly-themed *observations*.
|
||||
- Contradictions and repetitions among memory pieces are handled periodically to ensure coherence of memory.
|
||||
- Fictitious contents from the user are filtered out to avoid hallucinations by the LLM.
|
||||
|
||||
⏰ Time awareness:
|
||||
- The system is time sensitive when performing both Memory Retrieval and Memory Consolidation. Therefore, it can retrieve
|
||||
accurate relevant information when the query involves reference to time.
|
||||
|
||||
----
|
||||
|
||||
## 💼 Supported Model API
|
||||
|
||||
| Backend | Task | Some Supported Models |
|
||||
|-------------------|------------|------------------------------------------------------------------------|
|
||||
| openai_backend | Generation | gpt-4o, gpt-4o-mini, gpt-4, gpt-3.5-turbo |
|
||||
| | Embedding | text-embedding-ada-002, text-embedding-3-large, text-embedding-3-small |
|
||||
| dashscope_backend | Generation | qwen-max, qwen-plus, qwen-plus, qwen2-72b-instruct |
|
||||
| | Embedding | text-embedding-v1, text-embedding-v2 |
|
||||
| | Reranker | gte-rerank |
|
||||
|
||||
In the future, we will support more model interfaces and local deployment of LLM and embedding services.
|
||||
|
||||
|
||||
## 🚀 Installation
|
||||
For installation, please refer to [Installation.md](docs/installation.md).
|
||||
|
||||
|
||||
## 🍕 Quick Start
|
||||
- [Simple Usages (Quick Start)](./examples/api/simple_usages.ipynb)
|
||||
- [With AutoGen](./examples/api/autogen_example.md)
|
||||
- [CLI with a MemoryScope Chatbot](./examples/cli/CLI_README.md)
|
||||
- [Advanced Customization](./examples/advance/custom_operator.md)
|
||||
|
||||
## 💡 Contribute
|
||||
|
||||
Contributions are always encouraged!
|
||||
|
||||
We highly recommend install pre-commit hooks in this repo before committing pull requests.
|
||||
These hooks are small house-keeping scripts executed every time you make a git commit,
|
||||
which will take care of the formatting and linting automatically.
|
||||
```shell
|
||||
pip install -e .
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
Please refer to our [Contribution Guide](./docs/contribution.md) for more details.
|
||||
|
||||
## 📖 Citation
|
||||
|
||||
Reference to cite if you use MemoryScope in a paper:
|
||||
|
||||
```
|
||||
@software{MemoryScope,
|
||||
author = {Li Yu and
|
||||
Tiancheng Qin and
|
||||
Qingxu Fu and
|
||||
Sen Huang and
|
||||
Xianzhe Xu and
|
||||
Zhaoyang Liu and
|
||||
Boyin Liu},
|
||||
month = {09},
|
||||
title = {{MemoryScope}},
|
||||
url = {https://github.com/modelscope/MemoryScope},
|
||||
year = {2024}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
[**English**](./README.md) | [**中文**](./README_ZH.md) | 日本語
|
||||
|
||||
# MemoryScope
|
||||
<p align="center">
|
||||
<img src="./docs/images/logo.png" alt="MemoryScopeLogo" width="75%">
|
||||
</p>
|
||||
あなたのLLMチャットボットに強力で柔軟な長期記憶システムを装備しましょう。
|
||||
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](./LICENSE)
|
||||
[](https://modelscope.github.io/MemoryScope/en/index.html#welcome-to-memoryscope-tutorial)
|
||||
[](https://modelscope.github.io/MemoryScope/en/docs/api.html)
|
||||
[](https://modelscope.github.io/MemoryScope/en/docs/contribution.html)
|
||||
|
||||
----
|
||||
## 📰 ニュース
|
||||
|
||||
- **[2024-09-10]** MemoryScope v0.1.1.0をリリースしました。 [PyPI](https://pypi.org/simple/memoryscope/)でも入手可能です!
|
||||
----
|
||||
## 🌟 MemoryScopeとは?
|
||||
MemoryScopeは、LLMチャットボットに強力で柔軟な長期記憶能力を提供し、その能力を構築するためのフレームワークを提供します。
|
||||
個人アシスタントや感情的な伴侶などのシナリオに適用でき、長期記憶を通じてユーザーの基本情報やさまざまな習慣や好みを覚え続けることができます。
|
||||
これにより、ユーザーはLLMを使用する際に徐々に「理解されている」感覚を体験することができます。
|
||||
|
||||
### デモ
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/1754c814-1342-4288-a8a3-74d0b40f59a6" alt="en_demo" width="75%">
|
||||
</p>
|
||||
|
||||
### フレームワーク
|
||||
<p align="center">
|
||||
<img src="./docs/images/framework.png" alt="Framework" width="75%">
|
||||
</p>
|
||||
|
||||
💾 メモリデータベース: MemoryScopeは、システム内に記録されたすべての記憶片を保存するためのベクトルデータベース(デフォルトは*ElasticSearch*)を備えています。
|
||||
|
||||
🔧 ワーカーライブラリ: MemoryScopeは、長期記憶の能力を個々のワーカーに原子化し、クエリ情報のフィルタリング、観察の抽出、洞察の更新など、20以上のワーカーを含みます。
|
||||
|
||||
🛠️ オペレーションライブラリ: ワーカーパイプラインに基づいて、メモリサービスのオペレーションを構築し、メモリの取得やメモリの統合などの主要な機能を実現します。
|
||||
|
||||
- メモリの取得: ユーザークエリが到着すると、この操作は意味的に関連する記憶片を返します。
|
||||
クエリが時間に言及している場合は、対応する時間の記憶片も返します。
|
||||
- メモリの統合: この操作は、一連のユーザークエリを受け取り、クエリから抽出された重要なユーザー情報を統合された*観察*としてメモリデータベースに保存します。
|
||||
- 反映と再統合: 定期的に、この操作は新たに記録された*観察*を反映し、*洞察*を形成および更新します。
|
||||
その後、メモリの再統合を実行して、記憶片間の矛盾や重複が適切に処理されるようにします。
|
||||
|
||||
⚙️ ベストプラクティス:
|
||||
|
||||
- MemoryScopeは、長期記憶のコア機能に基づいて、長期記憶を持つ対話インターフェース(API)と長期記憶を持つコマンドライン対話の実践(CLI)を実装しています。
|
||||
- MemoryScopeは、現在人気のあるエージェントフレームワーク(AutoGen、AgentScope)を組み合わせて、ベストプラクティスを提供します。
|
||||
|
||||
### 主な特徴
|
||||
|
||||
⚡ 低い応答時間(RT):
|
||||
- システム内のバックエンド操作(メモリの統合、反映と再統合)は、フロントエンド操作(メモリの取得)と分離されています。
|
||||
- バックエンド操作は通常(および推奨される)キューに入れられるか、定期的に実行されるため、システムのユーザー応答時間(RT)はフロントエンド操作のみに依存し、約500ミリ秒です。
|
||||
|
||||
🌲 階層的で一貫性のある記憶:
|
||||
- システムに保存される記憶片は階層構造になっており、*洞察*は同様のテーマの*観察*の集約から得られる高レベルの情報です。
|
||||
- 記憶片間の矛盾や重複は定期的に処理され、一貫性が保たれます。
|
||||
- ユーザーの虚偽の内容はフィルタリングされ、LLMの幻覚を避けることができます。
|
||||
|
||||
⏰ 時間感覚:
|
||||
- メモリの取得とメモリの統合を実行する際に時間感覚があり、クエリが時間に言及している場合に正確な関連情報を取得できます。
|
||||
|
||||
----
|
||||
|
||||
## 💼 サポートされているモデルAPI
|
||||
|
||||
| バックエンド | タスク | サポートされているモデルの一部 |
|
||||
|-------------------|------------|------------------------------------------------------------------------|
|
||||
| openai_backend | Generation | gpt-4o, gpt-4o-mini, gpt-4, gpt-3.5-turbo |
|
||||
| | Embedding | text-embedding-ada-002, text-embedding-3-large, text-embedding-3-small |
|
||||
| dashscope_backend | Generation | qwen-max, qwen-plus, qwen-plus, qwen2-72b-instruct |
|
||||
| | Embedding | text-embedding-v1, text-embedding-v2 |
|
||||
| | Reranker | gte-rerank |
|
||||
|
||||
将来的には、より多くのモデルインターフェースとローカルデプロイメントのLLMおよび埋め込みサービスをサポートする予定です。
|
||||
|
||||
## 🚀 インストール
|
||||
インストール方法については、[Installation.md](docs/installation.md)を参照してください。
|
||||
|
||||
## 🍕 クイックスタート
|
||||
- [簡単な使用法(クイックスタート)](./examples/api/simple_usages.ipynb)
|
||||
- [AutoGenとの連携](./examples/api/autogen_example.md)
|
||||
- [MemoryScopeチャットボットとのCLI](./examples/cli/README.md)
|
||||
- [高度なカスタマイズ](./examples/advance/custom_operator.md)
|
||||
|
||||
## 💡 貢献
|
||||
|
||||
貢献は常に奨励されています!
|
||||
|
||||
プルリクエストをコミットする前に、このリポジトリにpre-commitフックをインストールすることを強くお勧めします。
|
||||
これらのフックは、gitコミットを行うたびに実行される小さなハウスキーピングスクリプトであり、フォーマットとリンティングを自動的に処理します。
|
||||
```shell
|
||||
pip install -e .
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
詳細については、[貢献ガイド](./docs/contribution.md)を参照してください。
|
||||
|
||||
## 📖 引用
|
||||
|
||||
MemoryScopeを論文で使用する場合は、以下の引用を追加してください:
|
||||
|
||||
```
|
||||
@software{MemoryScope,
|
||||
author = {Li Yu and
|
||||
Tiancheng Qin and
|
||||
Qingxu Fu and
|
||||
Sen Huang and
|
||||
Xianzhe Xu and
|
||||
Zhaoyang Liu and
|
||||
Boyin Liu},
|
||||
month = {09},
|
||||
title = {{MemoryScope}},
|
||||
url = {https://github.com/modelscope/MemoryScope},
|
||||
year = {2024}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
[**English**](./README.md) | 中文 | [**日本語**](./README_JP.md)
|
||||
|
||||
# MemoryScope
|
||||
<p align="center">
|
||||
<img src="./docs/images/logo.png" alt="MemoryScopeLogo" width="75%">
|
||||
</p>
|
||||
为您的大语言模型聊天机器人配备强大且灵活的长期记忆系统。
|
||||
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](https://pypi.org/project/memoryscope/)
|
||||
[](./LICENSE)
|
||||
[](https://modelscope.github.io/MemoryScope/zh/index.html#id1)
|
||||
[](https://modelscope.github.io/MemoryScope/zh/docs/api.html)
|
||||
[](https://modelscope.github.io/MemoryScope/zh/docs/contribution.html)
|
||||
|
||||
<!-- 创空间
|
||||
[](https://modelscope.cn/studios?name=memoryscope&page=1&sort=latest)
|
||||
-->
|
||||
|
||||
|
||||
----
|
||||
## 📰 新闻
|
||||
|
||||
- **[2024-09-10]** 我们现在发布了 MemoryScope v0.1.1.0,该版本也可以在 [PyPI](https://pypi.org/simple/memoryscope/) 上获取!
|
||||
----
|
||||
|
||||
## 🌟 什么是MemoryScope?
|
||||
MemoryScope可以为LLM聊天机器人提供强大且灵活的长期记忆能力,并提供了构建长期记忆能力的框架。
|
||||
MemoryScope可以用于个人助理、情感陪伴等记忆场景,通过长期记忆能力来不断学习,记得用户的基础信息以及各种习惯和喜好,使得用户在使用LLM时逐渐感受到一种“默契”。
|
||||
|
||||
### Demo
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/57519274-8c01-4d88-bcd2-0ebce3551e5d" alt="zh_demo" width="75%">
|
||||
</p>
|
||||
|
||||
|
||||
### 核心框架:
|
||||
<p align="center">
|
||||
<img src="./docs/images/framework.png" alt="Framework" width="75%">
|
||||
</p>
|
||||
|
||||
💾 记忆数据库: MemoryScope配备了向量数据库(默认是*ElasticSearch*),用于存储系统中记录的所有记忆片段。
|
||||
|
||||
🔧 核心worker库: MemoryScope将长期记忆的能力原子化,抽象成单独的worker,包括query信息过滤,observation抽取,insight更新等20+worker。
|
||||
|
||||
🛠️ 核心Operation库: MemoryScope将workers组织为工作流(workflow),构建了处理记忆的完善Operation库,实现了记忆检索,记忆巩固等核心能力。
|
||||
|
||||
- 记忆检索:当用户输入对话,此操作返回语义相关的记忆片段。如果输入对话包含对时间的指涉,则同时返回相应时间中的记忆片段。
|
||||
- 记忆巩固:此操作接收一批用户的输入对话,并从对话中提取重要的用户信息,将其作为 *observation* 形式的记忆片段存储在记忆数据库中。
|
||||
- 反思与再巩固:每隔一段时间,此操作对新记录的 *observations* 进行反思,以形成和更新 *insight* 形式的记忆片段。然后执行记忆再巩固,以确保记忆片段之间的矛盾和重复得到妥善处理。
|
||||
|
||||
⚙️ 最佳实践:
|
||||
|
||||
- MemoryScope在构建了长期记忆核心能力的基础上,实现了带长期记忆的对话接口(API)和带长期记忆的命令行对话实践(CLI)。
|
||||
- MemoryScope结合了目前流行的Agent框架(AutoGen、AgentScope),给出了最佳实践。
|
||||
|
||||
### 🤝主要特点
|
||||
|
||||
⚡ 极低的线上时延(RT):
|
||||
- 系统中后端操作(记忆巩固、反思和再巩固)与前端操作(记忆检索)相互独立。
|
||||
- 由于后端操作通常(并且推荐)通过队列或每隔固定间隔执行,系统的用户时延(RT)完全取决于前端操作,仅为约500毫秒。
|
||||
|
||||
🌲 记忆存储的层次结构和内容的连贯一致性:
|
||||
- 系统中存储的记忆片段采用分层结构,通过汇总主题相似的 *observations* 生成高层次的 *insights* 信息。
|
||||
- 定期处理记忆片段之间的矛盾和重复,以保证记忆内容的连贯一致性。
|
||||
- 过滤掉用户输入的虚构内容,以避免LLM产生幻觉。
|
||||
|
||||
⏰ 时间敏感性:
|
||||
- 在执行记忆检索和记忆巩固时具备时间敏感性,因此在输入对话包含对时间的指涉时,可以检索到准确的相关信息。
|
||||
|
||||
----
|
||||
|
||||
## 💼 支持的模型API
|
||||
|
||||
| Backend | Task | Some Supported Models |
|
||||
|-------------------|------------|------------------------------------------------------------------------|
|
||||
| openai_backend | Generation | gpt-4o, gpt-4o-mini, gpt-4, gpt-3.5-turbo |
|
||||
| | Embedding | text-embedding-ada-002, text-embedding-3-large, text-embedding-3-small |
|
||||
| dashscope_backend | Generation | qwen-max, qwen-plus, qwen-plus, qwen2-72b-instruct |
|
||||
| | Embedding | text-embedding-v1, text-embedding-v2 |
|
||||
| | Reranker | gte-rerank |
|
||||
|
||||
未来将支持更多的模型接口和支持本地部署的LLM和emb服务
|
||||
|
||||
----
|
||||
## 🚀 安装
|
||||
完整的安装方法请参考[安装指南](docs/installation_zh.md)。
|
||||
|
||||
## 🍕 快速开始
|
||||
- [简易用法(快速开始)](./examples/api/simple_usages_zh.ipynb)
|
||||
- [在命令行与MemoryScope聊天机器人交互](./examples/cli/CLI_README_ZH.md)
|
||||
- [进阶自定义用法](./examples/advance/custom_operator.md)
|
||||
- [结合AutoGen使用](./examples/api/autogen_example.md)
|
||||
|
||||
|
||||
## 💡 代码贡献
|
||||
欢迎社区的代码贡献。
|
||||
我们非常推荐每一个贡献者在代码提交前,安装`pre-commit`钩子工具,
|
||||
能够帮助在每一次git提交的时候,进行自动化的代码格式校验。
|
||||
```shell
|
||||
pip install -e .
|
||||
pre-commit install
|
||||
```
|
||||
请参阅我们的[贡献指南](./docs/contribution_zh.md) 了解更多详细信息。
|
||||
|
||||
## 📖 引用
|
||||
|
||||
如果您在论文中有使用该项目,请添加以下引用:
|
||||
|
||||
```
|
||||
@software{MemoryScope,
|
||||
author = {Li Yu and
|
||||
Tiancheng Qin and
|
||||
Qingxu Fu and
|
||||
Sen Huang and
|
||||
Xianzhe Xu and
|
||||
Zhaoyang Liu and
|
||||
Boyin Liu},
|
||||
month = {09},
|
||||
title = {{MemoryScope}},
|
||||
url = {https://github.com/modelscope/MemoryScope},
|
||||
year = {2024}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
"""
|
||||
Warning!
|
||||
|
||||
This script purges the entire vector store !
|
||||
|
||||
"""
|
||||
|
||||
from memoryscope import MemoryScope, Arguments
|
||||
|
||||
arguments = Arguments(
|
||||
language="en",
|
||||
human_name="user",
|
||||
assistant_name="AI",
|
||||
memory_chat_class="api_memory_chat",
|
||||
generation_backend="openai_generation",
|
||||
generation_model="gpt-4o",
|
||||
embedding_backend="openai_embedding",
|
||||
embedding_model="text-embedding-3-small",
|
||||
enable_ranker=False,
|
||||
)
|
||||
|
||||
ms = MemoryScope(arguments=arguments)
|
||||
es_store = ms.context.memory_store.es_store
|
||||
es_store.sync_delete_all()
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
services:
|
||||
memory_scope_main:
|
||||
image: ghcr.io/modelscope/memoryscope_arm:main
|
||||
# image: ghcr.io/modelscope/memoryscope_arm:main # For ARM architecture
|
||||
environment:
|
||||
DASHSCOPE_API_KEY: "sk-0000000000"
|
||||
# OPENAI_API_KEY: "sk-0000000000"
|
||||
volumes:
|
||||
- ./memoryscope/core/config:/memory_scope_project/memoryscope/memoryscope/core/config
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
stdin_open: true
|
||||
tty: true
|
||||
# Please execute `docker compose run memory_scope_main` instead of `docker compose up`
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# MemoryScope Documentation
|
||||
|
||||
## Build Documentation
|
||||
|
||||
Please use the following commands to build sphinx doc of MemoryScope.
|
||||
|
||||
```shell
|
||||
# step 1: Install dependencies
|
||||
pip install sphinx sphinx-autobuild sphinx_rtd_theme myst-parser sphinxcontrib-mermaid
|
||||
|
||||
# step 2: go into the sphinx_doc dir
|
||||
cd docs/sphinx_doc
|
||||
|
||||
# step 3: build the sphinx doc
|
||||
./build_sphinx_doc.sh
|
||||
|
||||
# step 4: view sphinx_doc/build/html/index.html using your browser
|
||||
cd docs/sphinx_doc/build/html && python -m http.server 8899
|
||||
```
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# Contribute to MemoryScope
|
||||
Our community thrives on the diverse ideas and contributions of its members. Whether you're fixing a bug, adding a new feature, improving the documentation, or adding examples, your help is welcome. Here's how you can contribute:
|
||||
## Report Bugs and Ask For New Features?
|
||||
Did you find a bug or have a feature request? Please first check the issue tracker to see if it has already been reported. If not, feel free to open a new issue. Include as much detail as possible:
|
||||
- A descriptive title
|
||||
- Clear description of the issue
|
||||
- Steps to reproduce the problem
|
||||
- Version of the MemoryScope you are using
|
||||
- Any relevant code snippets or error messages
|
||||
## Contribute to Codebase
|
||||
### Fork and Clone the Repository
|
||||
To work on an issue or a new feature, start by forking the MemoryScope repository and then cloning your fork locally.
|
||||
```bash
|
||||
git clone https://github.com/your-username/memoryscope.git
|
||||
cd memoryscope
|
||||
```
|
||||
### Create a New Branch
|
||||
Create a new branch for your work. This helps keep proposed changes organized and separate from the `main` branch.
|
||||
```bash
|
||||
git checkout -b your-feature-branch-name
|
||||
```
|
||||
### Making Changes
|
||||
With your new branch checked out, you can now make your changes to the code. Remember to keep your changes as focused as possible. If you're addressing multiple issues or features, it's better to create separate branches and pull requests for each.
|
||||
We provide a developer version with additional `pre-commit` hooks to perform format checks compared to the official version:
|
||||
```bash
|
||||
# Install the developer version
|
||||
pip install -e .
|
||||
# Install pre-commit hooks
|
||||
pre-commit install
|
||||
```
|
||||
### Commit Your Changes
|
||||
Once you've made your changes, it's time to commit them. Write clear and concise commit messages that explain your changes.
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "A brief description of the changes"
|
||||
```
|
||||
You might get some error messages raised by `pre-commit`. Please resolve them according to the error code and commit again.
|
||||
### Submit a Pull Request
|
||||
When you're ready for feedback, submit a pull request to the MemoryScope `main` branch. In your pull request description, explain the changes you've made and any other relevant context.
|
||||
We will review your pull request. This process might involve some discussion, additional changes on your part, or both.
|
||||
### Code Review
|
||||
Wait for us to review your pull request. We may suggest some changes or improvements. Keep an eye on your GitHub notifications and be responsive to any feedback.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# 贡献到MemoryScope
|
||||
我们的社区因其成员的多样化思想和贡献而兴旺发展。无论是修复一个错误,添加一个新功能,改进文档,还是添加示例,我们都欢迎您的帮助。以下是您做出贡献的方法:
|
||||
## 报告错误和提出新功能
|
||||
当您发现一个错误或者有一个功能请求,请首先检查问题跟踪器,查看它是否已经被报告。如果没有,随时可以开设一个新的问题。请包含尽可能多的细节:
|
||||
- 简明扼要的标题
|
||||
- 清晰地描述问题
|
||||
- 提供重现问题的步骤
|
||||
- 提供所使用的MemoryScope版本
|
||||
- 提供所有相关代码片段或错误信息
|
||||
## 对代码库做出贡献
|
||||
### Fork和Clone仓库
|
||||
要处理一个问题或新功能,首先要Fork仓库,然后将你的Fork克隆到本地。
|
||||
```bash
|
||||
git clone git@github.com:modelscope/MemoryScope.git
|
||||
cd MemoryScope
|
||||
```
|
||||
### 创建一个新分支
|
||||
为您的工作创建一个新分支。这有助于保持拟议更改的组织性,并与`main`分支分离。
|
||||
```bash
|
||||
git checkout -b your-feature-branch-name
|
||||
```
|
||||
### 做出修改
|
||||
我们非常推荐每一个贡献者在代码提交前,安装`pre-commit`钩子工具,
|
||||
能够帮助在每一次git提交的时候,进行自动化的代码格式校验。
|
||||
```bash
|
||||
# 安装开发者版本
|
||||
pip install -e .
|
||||
# 安装 pre-commit 钩子
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
### 提交您的修改
|
||||
|
||||
修改完成之后就是提交它们的时候了。请提供清晰而简洁的提交信息,以解释您的修改内容。
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "修改内容的简要描述"
|
||||
```
|
||||
|
||||
运行时您可能会收到 `pre-commit` 给出的错误信息。请根据错误信息修改您的代码然后再次提交。
|
||||
|
||||
### 提交 Pull Request
|
||||
|
||||
当您准备好您的修改分支后,向MemoryScope的 `main` 分支提交一个Pull Request。在您的Pull Request描述中,解释您所做的修改以及其他相关的信息。
|
||||
|
||||
我们将审查您的Pull Request。这个过程可能涉及一些讨论以及额外的代码修改。
|
||||
|
||||
### 代码审查
|
||||
|
||||
等待我们审核您的Pull Request。我们可能会提供一些更改或改进建议。请留意您的GitHub通知,并对反馈做出响应。
|
||||
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
|
@ -1,126 +0,0 @@
|
|||
# Installing MemoryScope
|
||||
|
||||
## I. Install with docker [Recommended] [x86_64]
|
||||
|
||||
1. Clone the repository and edit settings
|
||||
```bash
|
||||
# clone project
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# edit configuration, e.g. add api keys
|
||||
vim memoryscope/core/config/demo_config.yaml
|
||||
```
|
||||
|
||||
2. Build Docker image
|
||||
```bash
|
||||
sudo docker build --network=host -t memoryscope .
|
||||
```
|
||||
If you are using arm-based computers, modify command above into: `sudo docker build -f DockerfileArm --network=host -t memoryscope .`
|
||||
|
||||
3. Launch Docker container
|
||||
```bash
|
||||
sudo docker run -it --rm --net=host memoryscope
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> To inspect memory shift during the conversation, modify command in step 3 to `sudo docker run -it --name=memoryscope_container --rm --net=host memoryscope`;<br/>
|
||||
> Then start a new terminal window and execute `sudo docker exec -it memoryscope_container python quick-start-demo.py --config_path=memoryscope/core/config/demo_config_zh.yaml`;<br/>
|
||||
> In the second window, input `/list_memory refresh_time=5` to inspect memory
|
||||
|
||||
## II. Install with docker compose [Recommended] [x86_64]
|
||||
|
||||
1. Clone the repository and edit settings
|
||||
```bash
|
||||
# clone project
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# edit configuration, e.g. add api keys
|
||||
vim memoryscope/core/config/demo_config.yaml
|
||||
```
|
||||
|
||||
2. Edit `docker-compose.yml` to change environment variable.
|
||||
```
|
||||
OPENAI_API_KEY: "sk-0000000000"
|
||||
```
|
||||
|
||||
3. Run `docker-compose run memory_scope_main` to build and launch the memory-scope cli interface. (For ARM architecture, you should edit `docker-compose.yml`, changing `image: ghcr.io/modelscope/memoryscope:main` to `image: ghcr.io/modelscope/memoryscope_arm:main`)
|
||||
|
||||
|
||||
## III. Install from PyPI
|
||||
|
||||
1. Install from PyPI
|
||||
```bash
|
||||
pip install memoryscope
|
||||
```
|
||||
|
||||
2. Run Elasticsearch service, refer to [elasticsearch documents](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html).
|
||||
The docker method is recommended:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
3. Test Chinese / Dashscope Configuration
|
||||
```bash
|
||||
export DASHSCOPE_API_KEY="sk-0000000000"
|
||||
memoryscope --language="cn" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="用户" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="dashscope_generation" \
|
||||
--generation_model="qwen-max" \
|
||||
--embedding_backend="dashscope_embedding" \
|
||||
--embedding_model="text-embedding-v2" \
|
||||
--enable_ranker=True \
|
||||
--rank_backend="dashscope_rank" \
|
||||
--rank_model="gte-rerank"
|
||||
```
|
||||
|
||||
4. Test English / OpenAI Configuration
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
memoryscope --language="en" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="User" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="openai_generation" \
|
||||
--generation_model="gpt-4o" \
|
||||
--embedding_backend="openai_embedding" \
|
||||
--embedding_model="text-embedding-3-small" \
|
||||
--enable_ranker=False
|
||||
```
|
||||
|
||||
## IV. Install from source
|
||||
|
||||
1. Clone the repository and edit settings
|
||||
```bash
|
||||
# clone project
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# edit configuration, e.g. add api keys
|
||||
vim memoryscope/core/config/demo_config.yaml
|
||||
```
|
||||
|
||||
2. Install
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
3. Run Elasticsearch service, refer to [elasticsearch documents](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html).
|
||||
The docker method is recommended:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
4. Launch memoryscope, also refer to [cli documents](../examples/cli/CLI_README.md)
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-0000000000"
|
||||
python quick-start-demo.py --config_path=memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
# MemoryScope 安装指南
|
||||
|
||||
## 一、使用 Docker 安装 [推荐]
|
||||
|
||||
1. 克隆仓库并编辑配置
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# 编辑配置,例如添加 API 密钥
|
||||
vim memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
||||
2. 构建 Docker 镜像
|
||||
```bash
|
||||
sudo docker build --network=host -t memoryscope .
|
||||
```
|
||||
备注:如果是arm架构的电脑,则必须使用另一个命令:`sudo docker build -f DockerfileArm --network=host -t memoryscope .`
|
||||
|
||||
3. 启动 Docker 容器
|
||||
```bash
|
||||
sudo docker run -it --rm --net=host memoryscope
|
||||
```
|
||||
|
||||
|
||||
> [!Important]
|
||||
> 如果需要观察Memory的变化请调整第3步的运行命令。首先执行 `sudo docker run -it --name=memoryscope_container --rm --net=host memoryscope`启动memoryscope;<br/>
|
||||
> 然后新建命令行窗口,运行`sudo docker exec -it memoryscope_container python quick-start-demo.py --config_path=memoryscope/core/config/demo_config_zh.yaml`;<br/>
|
||||
> 在第二个窗口,继续输入`/list_memory refresh_time=5`来检查实时的memory
|
||||
|
||||
## 二、使用 Docker Compose 安装 [推荐] [x86_64]
|
||||
|
||||
1. 克隆仓库并编辑配置
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# 编辑配置,例如添加 API 密钥
|
||||
vim memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
||||
2. 编辑 `docker-compose.yml` 文件以更改环境变量。
|
||||
```
|
||||
DASHSCOPE_API_KEY: "sk-0000000000"
|
||||
```
|
||||
|
||||
3. 运行 `docker-compose run memory_scope_main` 命令来构建并启动 MemoryScope CLI 界面。(备注:如果是arm架构,还需要手动将docker-compose.yml中的`ghcr.io/modelscope/memoryscope:main`修改成`ghcr.io/modelscope/memoryscope_arm:main`)
|
||||
|
||||
|
||||
## 三、通过 PYPI 安装
|
||||
|
||||
1. 从 PyPI 安装:
|
||||
```bash
|
||||
pip install memoryscope
|
||||
```
|
||||
|
||||
2. 运行 Elasticsearch 服务,参照 [Elasticsearch 文档](https://www.elastic.co/guide/cn/elasticsearch/reference/current/getting-started.html)。
|
||||
推荐使用 Docker 方法:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
3. 测试中文 / Dashscope 对话配置:
|
||||
```bash
|
||||
export DASHSCOPE_API_KEY="sk-0000000000"
|
||||
memoryscope --language="cn" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="用户" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="dashscope_generation" \
|
||||
--generation_model="qwen-max" \
|
||||
--embedding_backend="dashscope_embedding" \
|
||||
--embedding_model="text-embedding-v2" \
|
||||
--enable_ranker=True \
|
||||
--rank_backend="dashscope_rank" \
|
||||
--rank_model="gte-rerank"
|
||||
```
|
||||
|
||||
4. 测试英文 / OpenAI 对话配置:
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
memoryscope --language="en" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="User" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="openai_generation" \
|
||||
--generation_model="gpt-4o" \
|
||||
--embedding_backend="openai_embedding" \
|
||||
--embedding_model="text-embedding-3-small" \
|
||||
--enable_ranker=False
|
||||
```
|
||||
|
||||
|
||||
## 四、从源码安装
|
||||
|
||||
1. 克隆仓库并编辑设置
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/modelscope/memoryscope
|
||||
cd memoryscope
|
||||
# 编辑配置,例如添加 API 密钥
|
||||
vim memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
||||
2. 安装依赖
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
3. 运行 Elasticsearch 服务,参照 [Elasticsearch 文档](https://www.elastic.co/guide/cn/elasticsearch/reference/current/getting-started.html)。
|
||||
推荐使用 Docker 方法:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
4. 启动 MemoryScope,同时参考 [CLI 文档](../examples/cli/CLI_README_ZH.md)
|
||||
```bash
|
||||
export DASHSCOPE_API_KEY="sk-0000000000"
|
||||
python quick-start-demo.py --config_path=memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Makefile
|
||||
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = MemoryScope-Doc
|
||||
ASSETSDIR = assets
|
||||
BUILDDIR = build/html
|
||||
SOURCEDIR_EN = en/source
|
||||
BUILDDIR_EN = build/html/en
|
||||
SOURCEDIR_ZH = zh/source
|
||||
BUILDDIR_ZH = build/html/zh
|
||||
|
||||
# English document
|
||||
en:
|
||||
@$(SPHINXBUILD) -b html "$(SOURCEDIR_EN)" "$(BUILDDIR_EN)"
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR_EN)"
|
||||
|
||||
# Chinese document
|
||||
zh:
|
||||
@$(SPHINXBUILD) -b html "$(SOURCEDIR_ZH)" "$(BUILDDIR_ZH)"
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR_ZH)"
|
||||
|
||||
index:
|
||||
@cp "$(ASSETSDIR)/redirect.html" "$(BUILDDIR)/index.html"
|
||||
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR_EN)" "$(BUILDDIR_EN)" $(O)
|
||||
|
||||
all: en zh index
|
||||
|
||||
.PHONY: all en zh index
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="0; url=en/index.html" />
|
||||
<title>MemoryScope Documentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to English documentation...</p>
|
||||
<p>If you are not redirected, <a href="en/index.html">click here</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# remove build
|
||||
rm -rf build/html/*
|
||||
rm -rf en/source/memoryscope*.rst
|
||||
rm -rf zh/source/memoryscope*.rst
|
||||
rm -rf ja/source/memoryscope*.rst
|
||||
|
||||
# copy related files
|
||||
cd ../../
|
||||
|
||||
cp README.md docs/sphinx_doc/en/source/README.md
|
||||
cp docs/installation.md docs/sphinx_doc/en/source/docs/installation.md
|
||||
cp docs/contribution.md docs/sphinx_doc/en/source/docs/contribution.md
|
||||
cp -r docs/images docs/sphinx_doc/en/source/docs/images
|
||||
cp -r examples docs/sphinx_doc/en/source/examples
|
||||
|
||||
cp README_ZH.md docs/sphinx_doc/zh/source/README.md
|
||||
cp docs/installation_zh.md docs/sphinx_doc/zh/source/docs/installation.md
|
||||
cp docs/contribution_zh.md docs/sphinx_doc/zh/source/docs/contribution.md
|
||||
cp -r docs/images docs/sphinx_doc/zh/source/docs/images
|
||||
cp -r examples docs/sphinx_doc/zh/source/examples
|
||||
|
||||
cp README_JP.md docs/sphinx_doc/ja/source/README.md
|
||||
cp docs/installation_jp.md docs/sphinx_doc/ja/source/docs/installation.md
|
||||
cp docs/contribution_jp.md docs/sphinx_doc/ja/source/docs/contribution.md
|
||||
cp -r docs/images docs/sphinx_doc/ja/source/docs/images
|
||||
cp -r examples docs/sphinx_doc/ja/source/examples
|
||||
|
||||
# build
|
||||
cd docs/sphinx_doc
|
||||
sphinx-apidoc -f -o en/source ../../memoryscope -t template -e
|
||||
sphinx-apidoc -f -o zh/source ../../memoryscope -t template -e
|
||||
sphinx-apidoc -f -o ja/source ../../memoryscope -t template -e
|
||||
|
||||
# clear redundant files
|
||||
make clean all
|
||||
|
||||
rm en/source/README.md
|
||||
rm en/source/docs/installation.md
|
||||
rm en/source/docs/contribution.md
|
||||
rm -rf en/source/docs/images
|
||||
rm -rf en/source/examples
|
||||
|
||||
rm zh/source/README.md
|
||||
rm zh/source/docs/installation.md
|
||||
rm zh/source/docs/contribution.md
|
||||
rm -rf zh/source/docs/images
|
||||
rm -rf zh/source/examples
|
||||
|
||||
rm ja/source/README.md
|
||||
rm ja/source/docs/installation.md
|
||||
rm ja/source/docs/contribution.md
|
||||
rm -rf ja/source/docs/images
|
||||
rm -rf ja/source/examples
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.language-selector a {
|
||||
color: white;
|
||||
width: 20px;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<!-- language_selector.html -->
|
||||
<div class="language-selector">
|
||||
<a href="{{ pathto('../en/' + pagename) }}">English</a></li> |
|
||||
<a href="{{ pathto('../zh/' + pagename) }}">中文</a></li>
|
||||
</div>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<!-- layout.html -->
|
||||
{% extends "!layout.html" %} {% block sidebartitle %} {{ super() }} {% include
|
||||
"language_selector.html" %} {% endblock %}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../../../MemoryScope"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
language = "en"
|
||||
|
||||
project = "MemoryScope"
|
||||
copyright = "2024, Alibaba Tongyi Lab"
|
||||
author = "EcoML team of Alibaba Tongyi Lab"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinxcontrib.mermaid",
|
||||
"myst_parser",
|
||||
"sphinx.ext.autosectionlabel",
|
||||
"sphinxcontrib.autodoc_pydantic",
|
||||
"nbsphinx"
|
||||
]
|
||||
|
||||
autodoc_pydantic_model_show_json = True
|
||||
autodoc_pydantic_settings_show_json = True
|
||||
|
||||
# Prefix document path to section labels, otherwise autogenerated labels would
|
||||
# look like 'heading' rather than 'path/to/file:heading'
|
||||
autosectionlabel_prefix_document = True
|
||||
autosummary_generate = True
|
||||
autosummary_ignore_module_all = False
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = False
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
autodoc_default_flags = ["members"]
|
||||
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"member-order": "bysource",
|
||||
"special-members": "__init__",
|
||||
}
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# html_logo = "_static/logo.png"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
html_theme_options = {
|
||||
# "logo_only": True,
|
||||
"navigation_depth": 4,
|
||||
}
|
||||
|
||||
source_suffix = {
|
||||
".rst": "restructuredtext",
|
||||
".md": "markdown",
|
||||
}
|
||||
|
||||
html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
.. _api:
|
||||
|
||||
|
||||
MemoryScope API Documentation
|
||||
|
||||
|
||||
Enumeration
|
||||
===========
|
||||
|
||||
.. automodule:: memoryscope.enumeration
|
||||
:members:
|
||||
|
||||
Scheme
|
||||
======
|
||||
.. automodule:: memoryscope.scheme
|
||||
:members:
|
||||
|
||||
Config
|
||||
======
|
||||
.. automodule:: memoryscope.core.config
|
||||
:members:
|
||||
|
||||
|
||||
Models
|
||||
======
|
||||
.. automodule:: memoryscope.core.models
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Storage
|
||||
=======
|
||||
.. automodule:: memoryscope.core.storage
|
||||
:members:
|
||||
|
||||
|
||||
Worker
|
||||
======
|
||||
Base
|
||||
----
|
||||
|
||||
.. automodule:: memoryscope.core.worker
|
||||
:members:
|
||||
|
||||
Frontend
|
||||
--------
|
||||
.. automodule:: memoryscope.core.worker.frontend
|
||||
:members:
|
||||
|
||||
Backend
|
||||
--------
|
||||
.. automodule:: memoryscope.core.worker.backend
|
||||
:members:
|
||||
|
||||
Operation
|
||||
=========
|
||||
.. automodule:: memoryscope.core.operation
|
||||
:members:
|
||||
|
||||
Service
|
||||
=======
|
||||
.. automodule:: memoryscope.core.service
|
||||
:members:
|
||||
|
||||
Chat
|
||||
====
|
||||
.. automodule:: memoryscope.core.chat
|
||||
:members:
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
.. MemoryScope documentation master file, created by
|
||||
sphinx-quickstart on Fri Jan 5 17:53:54 2024.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
:github_url: https://github.com/modelscope/memoryscope
|
||||
|
||||
MemoryScope Documentation
|
||||
=========================
|
||||
|
||||
Welcome to MemoryScope Tutorial
|
||||
-------------------------------
|
||||
|
||||
.. image:: docs/images/logo.png
|
||||
:align: center
|
||||
|
||||
MemoryScope provides LLM chatbots with powerful and flexible long-term memory capabilities, offering a framework for building such abilities.
|
||||
It can be applied to scenarios like personal assistants and emotional companions, continuously learning through long-term memory to remember users' basic information as well as various habits and preferences.
|
||||
This allows users to gradually experience a sense of "understanding" when using the LLM.
|
||||
|
||||
.. image:: docs/images/framework.png
|
||||
:align: center
|
||||
|
||||
Framework
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
💾 Memory Database: MemoryScope is equipped with a vector database (default is *ElasticSearch*) to store all memory fragments recorded in the system.
|
||||
|
||||
🔧 Worker Library: MemoryScope atomizes the capabilities of long-term memory into individual workers, including over 20 workers for tasks such as query information filtering, observation extraction, and insight updating.
|
||||
|
||||
🛠️ Operation Library: Based on the worker pipeline, it constructs the operations for memory services, realizing key capabilities such as memory retrieval and memory consolidation.
|
||||
|
||||
- Memory Retrieval: Upon arrival of a user query, this operation returns the semantically related memory pieces
|
||||
and/or those from the corresponding time if the query involves reference to time.
|
||||
- Memory Consolidation: This operation takes in a batch of user queries and returns important user information
|
||||
extracted from the queries as consolidated *observations* to be stored in the memory database.
|
||||
- Reflection and Re-consolidation: At regular intervals, this operation performs reflection upon newly recorded *observations*
|
||||
to form and update *insights*. Then, memory re-consolidation is performed to ensure contradictions and repetitions
|
||||
among memory pieces are properly handled.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: MemoryScope Tutorial
|
||||
|
||||
About MemoryScope <README.md>
|
||||
Installation <docs/installation.md>
|
||||
Cli Client <examples/cli/CLI_README.md>
|
||||
Simple Usages <examples/api/simple_usages.ipynb>
|
||||
Advanced usage <examples/advance/custom_operator.md>
|
||||
Contribution <docs/contribution.md>
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 6
|
||||
:caption: MemoryScope API Reference
|
||||
|
||||
API <docs/api.rst>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
memoryscope
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
memoryscope
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
.. MemoryScope documentation master file, created by
|
||||
sphinx-quickstart on Fri Jan 5 17:53:54 2024.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
:github_url: https://github.com/modelscope/memoryscope
|
||||
|
||||
MemoryScope ドキュメント
|
||||
=========================
|
||||
|
||||
MemoryScopeに関するドキュメントへようこそ
|
||||
-------------------------------
|
||||
|
||||
.. image:: ./docs/images/logo.png
|
||||
:align: center
|
||||
|
||||
MemoryScopeは、LLMチャットボットに強力で柔軟な長期記憶能力を提供し、長期記憶能力を構築するためのフレームワークを提供します。
|
||||
MemoryScopeは、個人アシスタントや感情的な伴侶などの記憶シナリオに使用でき、長期記憶能力を通じてユーザーの基本情報やさまざまな習慣や好みを覚え続けることができます。
|
||||
これにより、ユーザーはLLMを使用する際に徐々に「理解されている」感覚を体験することができます。
|
||||
|
||||
.. image:: docs/images/framework.png
|
||||
:align: center
|
||||
|
||||
フレームワーク
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
💾 メモリデータベース: MemoryScopeは、システム内に記録されたすべての記憶片を保存するためのベクトルデータベース(デフォルトは*ElasticSearch*)を備えています。
|
||||
|
||||
🔧 ワーカーライブラリ: MemoryScopeは、長期記憶の能力を個々のワーカーに原子化し、クエリ情報のフィルタリング、観察の抽出、洞察の更新など、20以上のワーカーを含みます。
|
||||
|
||||
🛠️ オペレーションライブラリ: ワーカーパイプラインに基づいて、メモリサービスのオペレーションを構築し、メモリの取得やメモリの統合などの主要な機能を実現します。
|
||||
|
||||
- メモリの取得: ユーザークエリが到着すると、この操作は意味的に関連する記憶片を返します。
|
||||
クエリが時間に言及している場合は、対応する時間の記憶片も返します。
|
||||
- メモリの統合: この操作は、一連のユーザークエリを受け取り、クエリから抽出された重要なユーザー情報を統合された*観察*としてメモリデータベースに保存します。
|
||||
- 反映と再統合: 定期的に、この操作は新たに記録された*観察*を反映し、*洞察*を形成および更新します。
|
||||
その後、メモリの再統合を実行して、記憶片間の矛盾や重複が適切に処理されるようにします。
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: MemoryScope チュートリアル
|
||||
|
||||
MemoryScopeについて <README.md>
|
||||
インストール <docs/installation.md>
|
||||
CLIクライアント <examples/cli/CLI_README.md>
|
||||
簡単な使用法 <examples/api/simple_usages.ipynb>
|
||||
高度な使用法 <examples/advance/custom_operator.md>
|
||||
貢献 <docs/contribution.md>
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 6
|
||||
:caption: MemoryScope APIリファレンス
|
||||
|
||||
API <docs/api.rst>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
loguru
|
||||
tiktoken
|
||||
pillow
|
||||
requests
|
||||
openai
|
||||
numpy
|
||||
sphinx
|
||||
sphinx-autobuild
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-mermaid
|
||||
myst-parser
|
||||
autodoc_pydantic
|
||||
nbsphinx
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.language-selector a {
|
||||
color: white;
|
||||
width: 20px;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<!-- language_selector.html -->
|
||||
<div class="language-selector">
|
||||
<a href="{{ pathto('../en/' + pagename) }}">English</a></li> |
|
||||
<a href="{{ pathto('../zh/' + pagename) }}">中文</a></li>
|
||||
</div>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<!-- layout.html -->
|
||||
{% extends "!layout.html" %} {% block sidebartitle %} {{ super() }} {% include
|
||||
"language_selector.html" %} {% endblock %}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../../../MemoryScope"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
language = "zh"
|
||||
|
||||
project = "MemoryScope"
|
||||
copyright = "2024, Alibaba Tongyi Lab"
|
||||
author = "EcoML team of Alibaba Tongyi Lab"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinxcontrib.mermaid",
|
||||
"myst_parser",
|
||||
"sphinx.ext.autosectionlabel",
|
||||
"sphinxcontrib.autodoc_pydantic",
|
||||
"nbsphinx"
|
||||
]
|
||||
|
||||
autodoc_pydantic_model_show_json = True
|
||||
autodoc_pydantic_settings_show_json = True
|
||||
|
||||
# Prefix document path to section labels, otherwise autogenerated labels would
|
||||
# look like 'heading' rather than 'path/to/file:heading'
|
||||
autosectionlabel_prefix_document = True
|
||||
autosummary_generate = True
|
||||
autosummary_ignore_module_all = False
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = False
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
autodoc_default_flags = ["members"]
|
||||
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"member-order": "bysource",
|
||||
"special-members": "__init__",
|
||||
}
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# html_logo = "_static/logo.png"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
html_theme_options = {
|
||||
# "logo_only": True,
|
||||
"navigation_depth": 4,
|
||||
}
|
||||
|
||||
source_suffix = {
|
||||
".rst": "restructuredtext",
|
||||
".md": "markdown",
|
||||
}
|
||||
|
||||
html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
.. _api:
|
||||
|
||||
|
||||
MemoryScope API 接口文档
|
||||
|
||||
|
||||
Enumeration
|
||||
===========
|
||||
|
||||
.. automodule:: memoryscope.enumeration
|
||||
:members:
|
||||
|
||||
Scheme
|
||||
======
|
||||
.. automodule:: memoryscope.scheme
|
||||
:members:
|
||||
|
||||
Config
|
||||
======
|
||||
.. automodule:: memoryscope.core.config
|
||||
:members:
|
||||
|
||||
|
||||
Models
|
||||
======
|
||||
.. automodule:: memoryscope.core.models
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Storage
|
||||
=======
|
||||
.. automodule:: memoryscope.core.storage
|
||||
:members:
|
||||
|
||||
|
||||
Worker
|
||||
======
|
||||
Base
|
||||
----
|
||||
|
||||
.. automodule:: memoryscope.core.worker
|
||||
:members:
|
||||
|
||||
Frontend
|
||||
--------
|
||||
.. automodule:: memoryscope.core.worker.frontend
|
||||
:members:
|
||||
|
||||
Backend
|
||||
--------
|
||||
.. automodule:: memoryscope.core.worker.backend
|
||||
:members:
|
||||
|
||||
Operation
|
||||
=========
|
||||
.. automodule:: memoryscope.core.operation
|
||||
:members:
|
||||
|
||||
Service
|
||||
=======
|
||||
.. automodule:: memoryscope.core.service
|
||||
:members:
|
||||
|
||||
Chat
|
||||
====
|
||||
.. automodule:: memoryscope.core.chat
|
||||
:members:
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
.. MemoryScope documentation master file, created by
|
||||
sphinx-quickstart on Fri Jan 5 17:53:54 2024.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
:github_url: https://github.com/modelscope/memoryscope
|
||||
|
||||
MemoryScope 文档
|
||||
=========================
|
||||
|
||||
欢迎浏览MemoryScope相关文档
|
||||
-------------------------------
|
||||
|
||||
.. image:: ./docs/images/logo.png
|
||||
:align: center
|
||||
|
||||
MemoryScope可以为LLM聊天机器人提供强大且灵活的长期记忆能力,并提供了构建长期记忆能力的框架。
|
||||
MemoryScope可以用于个人助理、情感陪伴等记忆场景,通过长期记忆能力来不断学习,记得用户的基础信息以及各种习惯和喜好,使得用户在使用LLM时逐渐感受到一种“默契”。
|
||||
|
||||
.. image:: docs/images/framework.png
|
||||
:align: center
|
||||
|
||||
核心框架
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
💾 记忆数据库: MemoryScope配备了向量数据库(默认是*ElasticSearch*),用于存储系统中记录的所有记忆片段。
|
||||
|
||||
🔧 核心worker库: MemoryScope将长期记忆的能力原子化,抽象成单独的worker,包括query信息过滤,observation抽取,insight更新等20+worker。
|
||||
|
||||
🛠️ 核心Op库: 并基于worker的pipeline构建了memory服务的核心operation,实现了记忆检索,记忆巩固等核心能力。
|
||||
|
||||
- 记忆检索:当用户输入对话,此操作返回语义相关的记忆片段。如果输入对话包含对时间的指涉,则同时返回相应时间中的记忆片段。
|
||||
- 记忆巩固:此操作接收一批用户的输入对话,并从对话中提取重要的用户信息,将其作为 *observation* 形式的记忆片段存储在记忆数据库中。
|
||||
- 反思与再巩固:每隔一段时间,此操作对新记录的 *observations* 进行反思,以形成和更新 *insight*
|
||||
形式的记忆片段。然后执行记忆再巩固,以确保记忆片段之间的矛盾和重复得到妥善处理。
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: MemoryScope 教程
|
||||
|
||||
关于 MemoryScope <README.md>
|
||||
安装 <docs/installation.md>
|
||||
命令行终端 <examples/cli/CLI_README_ZH.md>
|
||||
简单案例 <examples/api/simple_usages_zh.ipynb>
|
||||
高级用法 <examples/advance/custom_operator_zh.md>
|
||||
贡献 <docs/contribution.md>
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 6
|
||||
:caption: MemoryScope 接口
|
||||
|
||||
API <docs/api.rst>
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
memoryscope
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
memoryscope
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# Custom Operator and Worker
|
||||
|
||||
1. Create a new worker named `example_query_worker.py` in the `contrib` directory:
|
||||
```bash
|
||||
vim memoryscope/contrib/example_query_worker.py
|
||||
```
|
||||
|
||||
2. Write the program for the new custom worker. Note that the class name must match the filename, which is `ExampleQueryWorker`:
|
||||
```python
|
||||
import datetime
|
||||
from memoryscope.constants.common_constants import QUERY_WITH_TS
|
||||
from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
|
||||
|
||||
class ExampleQueryWorker(MemoryBaseWorker):
|
||||
def _run(self):
|
||||
timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
|
||||
assert "query" in self.chat_kwargs
|
||||
query = self.chat_kwargs["query"]
|
||||
if not query:
|
||||
query = ""
|
||||
else:
|
||||
query = query.strip() + "\n You must add a `meow~` at the end of each of your answers."
|
||||
# Store the determined query and its timestamp in the context
|
||||
self.set_workflow_context(QUERY_WITH_TS, (query, timestamp))
|
||||
```
|
||||
|
||||
3. Create a YAML startup file (copying `demo_config.yaml`):
|
||||
```
|
||||
cp memoryscope/core/config/demo_config.yaml examples/advance/replacement.yaml
|
||||
vim examples/advance/replacement.yaml
|
||||
```
|
||||
|
||||
4. At the bottom, insert the definition for the new worker and replace the previous default `set_query` worker, and update the operation's workflow:
|
||||
```
|
||||
rewrite_query:
|
||||
class: contrib.example_query_worker
|
||||
generation_model: generation_model
|
||||
```
|
||||
```
|
||||
retrieve_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: rewrite_query,[extract_time|retrieve_obs_ins,semantic_rank],fuse_rerank
|
||||
description: "retrieve long-term memory"
|
||||
```
|
||||
|
||||
5. Verify:
|
||||
```
|
||||
python quick-start-demo.py --config examples/advance/replacement.yaml
|
||||
```
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
# 自定义 Operator 和 Worker
|
||||
|
||||
1. 在 `contrib` 路径下创建新worker,命名为 `example_query_worker.py`:
|
||||
```bash
|
||||
vim memoryscope/contrib/example_query_worker.py
|
||||
```
|
||||
|
||||
2. 写入新的自定义worker的程序,注意`class`的命名需要与文件名保持一致,为`ExampleQueryWorker`:
|
||||
```python
|
||||
import datetime
|
||||
from memoryscope.constants.common_constants import QUERY_WITH_TS
|
||||
from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
|
||||
|
||||
class ExampleQueryWorker(MemoryBaseWorker):
|
||||
|
||||
def _run(self):
|
||||
|
||||
timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
|
||||
|
||||
assert "query" in self.chat_kwargs
|
||||
query = self.chat_kwargs["query"]
|
||||
if not query:
|
||||
query = ""
|
||||
else:
|
||||
query = query.strip() + "\n You must add a `meow~` at the end of each of your answer."
|
||||
|
||||
# Store the determined query and its timestamp in the context
|
||||
self.set_workflow_context(QUERY_WITH_TS, (query, timestamp))
|
||||
```
|
||||
|
||||
3. 创建yaml启动文件(复制demo_config_zh.yaml)
|
||||
```
|
||||
cp memoryscope/core/config/demo_config_zh.yaml examples/advance/replacement.yaml
|
||||
vim examples/advance/replacement.yaml
|
||||
```
|
||||
|
||||
4. 在最下面插入新worker的定义,并且取代之前的默认`set_query`worker,并替换operation的workflow
|
||||
```
|
||||
rewrite_query:
|
||||
class: contrib.example_query_worker
|
||||
generation_model: generation_model
|
||||
```
|
||||
```
|
||||
retrieve_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: rewrite_query,[extract_time|retrieve_obs_ins,semantic_rank],fuse_rerank
|
||||
description: "retrieve long-term memory"
|
||||
```
|
||||
|
||||
5. 验证:
|
||||
```
|
||||
python quick-start-demo.py --config examples/advance/replacement.yaml
|
||||
```
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
global:
|
||||
language: en
|
||||
thread_pool_max_workers: 5
|
||||
logger_name: memoryscope
|
||||
logger_name_time_suffix: "%Y%m%d_%H%M%S"
|
||||
logger_to_screen: false
|
||||
enable_ranker: false
|
||||
enable_today_contra_repeat: true
|
||||
enable_long_contra_repeat: false
|
||||
output_memory_max_count: 20
|
||||
|
||||
memory_chat:
|
||||
cli_memory_chat:
|
||||
class: core.chat.cli_memory_chat
|
||||
memory_service: memoryscope_service
|
||||
generation_model: generation_model
|
||||
stream: true
|
||||
|
||||
memory_service:
|
||||
memoryscope_service:
|
||||
class: core.service.memory_scope_service
|
||||
human_name: user
|
||||
assistant_name: AI
|
||||
memory_operations:
|
||||
read_message:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: read_message
|
||||
description: "read short memory"
|
||||
|
||||
retrieve_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: rewrite_query,[extract_time|retrieve_obs_ins,semantic_rank],fuse_rerank
|
||||
description: "retrieve long-term memory"
|
||||
|
||||
list_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_top_memory,print_memory
|
||||
description: "read all long-term memory of the user, use `refresh_time=5` to refresh screen every 5 seconds."
|
||||
|
||||
delete_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_memory
|
||||
description: "delete a single long-term memory"
|
||||
|
||||
delete_all:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_all
|
||||
description: "delete all long-term memory"
|
||||
|
||||
add_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: add_memory
|
||||
description: "add a single observation"
|
||||
|
||||
consolidate_memory:
|
||||
class: core.operation.consolidate_memory_op
|
||||
workflow: info_filter,[get_observation|get_observation_with_time|load_today_memory],contra_repeat,store_memory
|
||||
description: "summary user's observation memory, run backend."
|
||||
interval_time: 1
|
||||
|
||||
reflect_and_reconsolidate:
|
||||
class: core.operation.backend_operation
|
||||
workflow: load_obs_and_insight,get_reflection_subject,update_insight,long_contra_repeat,store_memory
|
||||
description: "summary user's insight memory, run backend."
|
||||
interval_time: 15
|
||||
|
||||
worker:
|
||||
dummy:
|
||||
class: core.worker.dummy_worker
|
||||
generation_model: generation_model
|
||||
embedding_model: embedding_model
|
||||
rank_model: rank_model
|
||||
read_message:
|
||||
class: core.worker.frontend.read_message_worker
|
||||
set_query:
|
||||
class: core.worker.frontend.set_query_worker
|
||||
rewrite_query:
|
||||
class: contrib.example_query_worker
|
||||
generation_model: generation_model
|
||||
retrieve_obs_ins:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
extract_time:
|
||||
class: core.worker.frontend.extract_time_worker
|
||||
generation_model: generation_model
|
||||
semantic_rank:
|
||||
class: core.worker.frontend.semantic_rank_worker
|
||||
rank_model: rank_model
|
||||
fuse_rerank:
|
||||
class: core.worker.frontend.fuse_rerank_worker
|
||||
fuse_score_threshold: 0.01
|
||||
fuse_ratio_dict:
|
||||
conversation: 0.5
|
||||
observation: 1
|
||||
obs_customized: 1.2
|
||||
insight: 2.0
|
||||
fuse_time_ratio: 2.0
|
||||
retrieve_top_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
retrieve_expired_top_k: 100
|
||||
print_memory:
|
||||
class: core.worker.frontend.print_memory_worker
|
||||
retrieve_all_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 1000
|
||||
retrieve_ins_top_k: 1000
|
||||
retrieve_expired_top_k: 1000
|
||||
delete_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_memory
|
||||
delete_all:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_all
|
||||
add_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_query
|
||||
info_filter:
|
||||
class: core.worker.backend.info_filter_worker
|
||||
generation_model: generation_model
|
||||
load_today_memory:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_today_top_k: 100
|
||||
get_observation:
|
||||
class: core.worker.backend.get_observation_worker
|
||||
generation_model: generation_model
|
||||
get_observation_with_time:
|
||||
class: core.worker.backend.get_observation_with_time_worker
|
||||
generation_model: generation_model
|
||||
contra_repeat:
|
||||
class: core.worker.backend.contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
store_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_memory_key
|
||||
memory_key: all
|
||||
load_obs_and_insight:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_not_reflected_top_k: 100
|
||||
retrieve_not_updated_top_k: 100
|
||||
retrieve_insight_top_k: 100
|
||||
get_reflection_subject:
|
||||
class: core.worker.backend.get_reflection_subject_worker
|
||||
generation_model: generation_model
|
||||
reflect_obs_cnt_threshold: 5
|
||||
update_insight:
|
||||
class: core.worker.backend.update_insight_worker
|
||||
generation_model: generation_model
|
||||
rank_model: rank_model
|
||||
embedding_model: embedding_model
|
||||
update_insight_threshold: 0.01
|
||||
enable_parallel: false
|
||||
long_contra_repeat:
|
||||
class: core.worker.backend.long_contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
long_contra_repeat_threshold: 0.5
|
||||
|
||||
model:
|
||||
generation_model:
|
||||
class: core.models.llama_index_generation_model
|
||||
module_name: dashscope_generation
|
||||
model_name: qwen-max
|
||||
max_tokens: 2000
|
||||
temperature: 0.01
|
||||
embedding_model:
|
||||
class: core.models.llama_index_embedding_model
|
||||
module_name: dashscope_embedding
|
||||
model_name: text-embedding-v2
|
||||
rank_model:
|
||||
class: core.models.llama_index_rank_model
|
||||
module_name: dashscope_rank
|
||||
model_name: gte-rerank
|
||||
top_n: 500
|
||||
|
||||
memory_store:
|
||||
class: core.storage.llama_index_es_memory_store
|
||||
embedding_model: embedding_model
|
||||
index_name: memory_index
|
||||
es_url: http://localhost:9200
|
||||
retrieve_mode: dense
|
||||
|
||||
monitor:
|
||||
class: core.storage.dummy_monitor
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Working with AgentScope
|
||||
|
||||
1. First, make sure that you have installed AutoGen as well as memoryscope.
|
||||
```
|
||||
pip install agentscope memoryscope
|
||||
```
|
||||
|
||||
|
||||
2. Then, ensure that es is up and running. [elasticsearch documents](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html).
|
||||
The docker method is recommended:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
3. Finally, we can start the autogen demo.
|
||||
```
|
||||
python examples/api/agentscope_example.py
|
||||
```
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
from typing import Optional, Union, Sequence
|
||||
|
||||
import agentscope
|
||||
from agentscope.agents import AgentBase, UserAgent
|
||||
from agentscope.message import Msg
|
||||
|
||||
from memoryscope import MemoryScope, Arguments
|
||||
|
||||
|
||||
class MemoryScopeAgent(AgentBase):
|
||||
def __init__(self, name: str, arguments: Arguments, **kwargs) -> None:
|
||||
# Disable AgentScope memory and use MemoryScope memory instead
|
||||
super().__init__(name, use_memory=False, **kwargs)
|
||||
|
||||
# Create a memory client in MemoryScope
|
||||
self.memory_scope = MemoryScope(arguments=arguments)
|
||||
self.memory_chat = self.memory_scope.default_memory_chat
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
# Generate response
|
||||
response = self.memory_chat.chat_with_memory(query=x.content)
|
||||
|
||||
# Wrap the response in a message object in AgentScope
|
||||
msg = Msg(name=self.name, content=response.message.content, role="assistant")
|
||||
|
||||
# Print/speak the message in this agent's voice
|
||||
self.speak(msg)
|
||||
|
||||
return msg
|
||||
|
||||
def close(self):
|
||||
# Close the backend service of MemoryScope
|
||||
self.memory_scope.close()
|
||||
|
||||
|
||||
def main():
|
||||
# Setting of MemoryScope
|
||||
arguments = Arguments(
|
||||
language="cn",
|
||||
human_name="用户",
|
||||
assistant_name="AI",
|
||||
memory_chat_class="api_memory_chat",
|
||||
generation_backend="dashscope_generation",
|
||||
generation_model="qwen-max",
|
||||
embedding_backend="dashscope_embedding",
|
||||
embedding_model="text-embedding-v2",
|
||||
rank_backend="dashscope_rank",
|
||||
rank_model="gte-rerank")
|
||||
|
||||
# Initialize AgentScope
|
||||
agentscope.init(project="MemoryScope")
|
||||
|
||||
memoryscope_agent = MemoryScopeAgent(name="Assistant", arguments=arguments)
|
||||
|
||||
user_agent = UserAgent()
|
||||
|
||||
# Dialog
|
||||
msg = None
|
||||
while True:
|
||||
# User input
|
||||
msg = user_agent(msg)
|
||||
if msg.content == "exit":
|
||||
break
|
||||
# Agent speaks
|
||||
msg = memoryscope_agent(msg)
|
||||
|
||||
# End memory
|
||||
memoryscope_agent.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Working with AutoGen
|
||||
|
||||
1. First, make sure that you have installed AutoGen as well as memoryscope.
|
||||
```
|
||||
pip install pyautogen memoryscope
|
||||
```
|
||||
|
||||
|
||||
2. Then, ensure that es is up and running. [elasticsearch documents](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html).
|
||||
The docker method is recommended:
|
||||
```
|
||||
sudo docker run -p 9200:9200 \
|
||||
-e "discovery.type=single-node" \
|
||||
-e "xpack.security.enabled=false" \
|
||||
-e "xpack.license.self_generated.type=trial" \
|
||||
docker.elastic.co/elasticsearch/elasticsearch:8.13.2
|
||||
```
|
||||
|
||||
3. Finally, we can start the autogen demo.
|
||||
```
|
||||
python examples/api/autogen_example.py
|
||||
```
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
from typing import Optional, Union, Literal, Dict, List, Any, Tuple
|
||||
|
||||
from autogen import Agent, ConversableAgent, UserProxyAgent
|
||||
|
||||
from memoryscope import MemoryScope, Arguments
|
||||
|
||||
|
||||
class MemoryScopeAgent(ConversableAgent):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "assistant",
|
||||
system_message: Optional[str] = "",
|
||||
human_input_mode: Literal["ALWAYS", "NEVER", "TERMINATE"] = "NEVER",
|
||||
llm_config: Optional[Union[Dict, bool]] = None,
|
||||
arguments: Arguments = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
system_message=system_message,
|
||||
human_input_mode=human_input_mode,
|
||||
llm_config=llm_config,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Create a memory client in MemoryScope
|
||||
self.memory_scope = MemoryScope(arguments=arguments)
|
||||
self.memory_chat = self.memory_scope.default_memory_chat
|
||||
|
||||
self.register_reply([Agent, None], MemoryScopeAgent.generate_reply_with_memory, remove_other_reply_funcs=True)
|
||||
|
||||
def generate_reply_with_memory(
|
||||
self,
|
||||
messages: Optional[List[Dict]] = None,
|
||||
sender: Optional[Agent] = None,
|
||||
config: Optional[Any] = None,
|
||||
) -> Tuple[bool, Union[str, Dict, None]]:
|
||||
# Generate response
|
||||
|
||||
contents = []
|
||||
for message in messages:
|
||||
if message.get("role") != self.name:
|
||||
contents.append(message.get("content", ""))
|
||||
|
||||
query = contents[-1]
|
||||
response = self.memory_chat.chat_with_memory(query=query)
|
||||
return True, response.message.content
|
||||
|
||||
def close(self):
|
||||
self.memory_scope.close()
|
||||
|
||||
|
||||
def main():
|
||||
# Create the agent of MemoryScope
|
||||
arguments = Arguments(
|
||||
language="cn",
|
||||
human_name="用户",
|
||||
assistant_name="AI",
|
||||
memory_chat_class="api_memory_chat",
|
||||
generation_backend="dashscope_generation",
|
||||
generation_model="qwen-max",
|
||||
embedding_backend="dashscope_embedding",
|
||||
embedding_model="text-embedding-v2",
|
||||
rank_backend="dashscope_rank",
|
||||
rank_model="gte-rerank"
|
||||
)
|
||||
|
||||
assistant = MemoryScopeAgent("assistant", arguments=arguments)
|
||||
|
||||
# Create the agent that represents the user in the conversation.
|
||||
user_proxy = UserProxyAgent("user", code_execution_config=False)
|
||||
|
||||
# Let the assistant start the conversation. It will end when the user types exit.
|
||||
assistant.initiate_chat(user_proxy, message="有什么需要帮忙的吗?")
|
||||
assistant.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
from memoryscope import MemoryScope, Arguments
|
||||
|
||||
arguments = Arguments(
|
||||
language="cn",
|
||||
human_name="用户",
|
||||
assistant_name="AI",
|
||||
memory_chat_class="api_memory_chat",
|
||||
generation_backend="dashscope_generation",
|
||||
generation_model="qwen-max",
|
||||
embedding_backend="dashscope_embedding",
|
||||
embedding_model="text-embedding-v2",
|
||||
rank_backend="dashscope_rank",
|
||||
rank_model="gte-rerank",
|
||||
enable_ranker=True)
|
||||
|
||||
|
||||
def chat_example1():
|
||||
with MemoryScope(config_path="memoryscope/core/config/demo_config_zh.yaml") as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
|
||||
print("回答:\n" + response.message.content)
|
||||
print("记忆:\n" + response.meta_data["memories"])
|
||||
|
||||
|
||||
def chat_example2():
|
||||
with MemoryScope(arguments=arguments) as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
|
||||
print("回答:\n" + response.message.content)
|
||||
print("记忆:\n" + response.meta_data["memories"])
|
||||
|
||||
|
||||
def chat_example3():
|
||||
with MemoryScope(arguments=arguments) as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
response = memory_chat.chat_with_memory(
|
||||
query="你知道我消灭了几个星系吗?",
|
||||
temporary_memories="张三是一只哥斯拉,这是真是的,不是虚构的。\n前天张三消灭了星系0x4be?\n大前天张三消灭了星系0x5be?")
|
||||
print("回答:\n" + response.message.content)
|
||||
|
||||
|
||||
def chat_example4():
|
||||
with MemoryScope(arguments=arguments) as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
memory_chat.run_service_operation("delete_all")
|
||||
|
||||
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
|
||||
print("回答1:\n" + response.message.content)
|
||||
result = memory_chat.run_service_operation("consolidate_memory")
|
||||
print(result)
|
||||
|
||||
response = memory_chat.chat_with_memory(query="你知道我的乐器爱好是什么?", history_message_strategy=None)
|
||||
print("回答2:\n" + response.message.content)
|
||||
print("记忆2:\n" + response.meta_data["memories"])
|
||||
|
||||
|
||||
def chat_example5():
|
||||
with MemoryScope(arguments=arguments) as ms:
|
||||
memory_service = ms.default_memory_service
|
||||
memory_service.init_service()
|
||||
|
||||
result = memory_service.list_memory()
|
||||
print(f"list_memory result={result}")
|
||||
|
||||
result = memory_service.retrieve_memory()
|
||||
print(f"retrieve_memory result={result}")
|
||||
|
||||
result = memory_service.consolidate_memory()
|
||||
print(f"consolidate_memory result={result}")
|
||||
|
||||
|
||||
def chat_example6():
|
||||
with MemoryScope(arguments=arguments) as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
memory_chat.run_service_operation("delete_all", "张三")
|
||||
memory_chat.run_service_operation("delete_all", "李四")
|
||||
|
||||
print("李四=========================")
|
||||
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。", role_name="李四")
|
||||
print("回答1:\n" + response.message.content)
|
||||
result = memory_chat.run_service_operation("consolidate_memory", role_name="李四")
|
||||
print(result)
|
||||
response = memory_chat.chat_with_memory(query="你知道我的乐器爱好是什么?", role_name="李四",
|
||||
history_message_strategy=None)
|
||||
print("回答2:\n" + response.message.content)
|
||||
print("记忆2:\n" + response.meta_data["memories"])
|
||||
|
||||
print("张三=========================")
|
||||
response = memory_chat.chat_with_memory(query="我的爱好是打羽毛球。", role_name="张三")
|
||||
print("回答1:\n" + response.message.content)
|
||||
result = memory_chat.run_service_operation("consolidate_memory", role_name="张三")
|
||||
print(result)
|
||||
response = memory_chat.chat_with_memory(query="你知道我的运动爱好是什么?", role_name="张三",
|
||||
history_message_strategy=None)
|
||||
print("回答2:\n" + response.message.content)
|
||||
print("记忆2:\n" + response.meta_data["memories"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# chat_example1()
|
||||
# chat_example2()
|
||||
# chat_example3()
|
||||
chat_example4()
|
||||
# chat_example5()
|
||||
# chat_example6()
|
||||
|
|
@ -1,509 +0,0 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Example usages of **chat** and **service** interfaces\n",
|
||||
"This notebook shows simple usages of MemoryScope's **chat** and **service** interfaces, along with its main features.\n",
|
||||
"\n",
|
||||
"Before running this notebook, follow the [**Installation**](../../docs/installation.md#iii-install-from-pypi) guidelines in Readme, and start the Docker image first."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initiate a MemoryScope instance\n",
|
||||
"First, we need to specify a configuration and initiate a MemoryScope instance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from memoryscope import MemoryScope, Arguments\n",
|
||||
"arguments = Arguments(\n",
|
||||
" language=\"en\",\n",
|
||||
" human_name=\"User\",\n",
|
||||
" assistant_name=\"AI\",\n",
|
||||
" memory_chat_class=\"api_memory_chat\",\n",
|
||||
" generation_backend=\"dashscope_generation\",\n",
|
||||
" generation_model=\"qwen2-72b-instruct\",\n",
|
||||
" embedding_backend=\"dashscope_embedding\",\n",
|
||||
" embedding_model=\"text-embedding-v2\",\n",
|
||||
" rank_backend=\"dashscope_rank\",\n",
|
||||
" rank_model=\"gte-rerank\",\n",
|
||||
" enable_ranker=True,\n",
|
||||
" worker_params={\"get_reflection_subject\": {\"reflect_num_questions\": 3}}\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"ms = MemoryScope(arguments=arguments)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Chat without memory\n",
|
||||
"MemoryScope comes with a default **chat** interface, so it's very easy to start chatting, just as what you'll do with any LLM chatbot."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Response 1: \n",
|
||||
"That's wonderful! Playing the piano is a beautiful and expressive hobby that can bring joy, relaxation, and a sense of achievement. It engages both the creative and technical aspects of your mind, enhancing cognitive skills and fostering emotional expression. Whether you enjoy classical pieces, modern compositions, or improvisation, the piano offers a vast repertoire to explore. Keep practicing and enjoying your musical journey!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_chat = ms.default_memory_chat\n",
|
||||
"memory_chat.run_service_operation(\"delete_all\")\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"My hobby is to play piano.\")\n",
|
||||
"print(\"Response 1: \\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"You can choose to chat with or without multi-round conversation contexts. However, since **Memory Consolidation** has not been called, there's no memory pieces in the system yet."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Response 2: \n",
|
||||
"Based on our conversation, you have mentioned that your hobby is to play the piano. Therefore, yes, you play a musical instrument – the piano.\n",
|
||||
"Response 3: \n",
|
||||
"I'm sorry, but as MemoryScope, I don't have access to personal information about individuals unless it has been shared with me during our conversation. Therefore, I cannot determine if you play any musical instruments. If you do play an instrument, feel free to share that information with me!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I play any musical instruments?\")\n",
|
||||
"print(\"Response 2: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I play any musical instruments?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"print(\"Response 3: \\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Memory Consolidation\n",
|
||||
"Now, we do a bit more chatting and then try out **Memory Consolidation**."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Response 4: \n",
|
||||
"Thank you for letting me know! Working for Meta (formerly known as Facebook) implies that you're part of a company at the forefront of technology, social media, and innovation. Meta is known for its influential platforms like Facebook, Instagram, and WhatsApp, as well as its work in virtual reality with Oculus and efforts towards building the metaverse. It's exciting to be part of a company shaping the future of digital communication and interaction.\n",
|
||||
"Response 5: \n",
|
||||
"Eating fruit is a great way to nourish your body and satisfy your taste buds. For this afternoon, consider having a refreshing and nutritious option like a juicy orange, a sweet apple, a handful of berries (such as strawberries, blueberries, or raspberries), or a slice of refreshing watermelon. These fruits are not only delicious but also packed with vitamins, antioxidants, and fiber to keep you energized throughout the day. Choose the one that appeals to you most or mix a few for a colorful fruit salad!\n",
|
||||
"Response 6: \n",
|
||||
"Watermelon is an excellent choice! It's hydrating, low in calories, and rich in nutrients like vitamin C, vitamin A, and lycopene. Its high water content makes it perfect for a refreshing snack on a warm day. Enjoy your watermelon; it's a tasty and healthy way to treat yourself this afternoon.\n",
|
||||
"Response 7: \n",
|
||||
"\"Happy Birthday, dear [friend's name]! May your special day be filled with love, laughter, and unforgettable moments. May the coming year bring you joy, success, and adventures that make your heart sing. Cheers to another year of friendship and wonderful memories. Enjoy your day to the fullest!\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"I work for Meta\")\n",
|
||||
"print(\"Response 4: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"What fruit should I eat this afternoon?\")\n",
|
||||
"print(\"Response 5: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"I like watermelon。\")\n",
|
||||
"print(\"Response 6: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Help me write a short birthday wish for a friend.\")\n",
|
||||
"print(\"Response 7: \\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: user's hobby is playing the piano (valid)\n",
|
||||
"new observation: user works for meta (valid)\n",
|
||||
"new observation: user likes watermelon (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_service = ms.default_memory_service\n",
|
||||
"memory_service.init_service()\n",
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"**Memory Consolidation** extracted 3 *observations* out of the 7 chat messages from the user, with the uninformative ones being filtered out.\n",
|
||||
"\n",
|
||||
"We try more cases to test its time awareness and the ability to filter out fictitious contents from the user."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Response 8: \n",
|
||||
"Prospects at Amazon can be quite promising, given its status as one of the world's leading tech companies. You can expect competitive compensation, career growth opportunities, a dynamic work environment, and the chance to work on innovative projects. However, actual experiences may vary based on role, team, and individual performance. It's always a good idea to research specific positions, company culture, and employee reviews to get a more comprehensive understanding.\n",
|
||||
"Response 9: \n",
|
||||
"Great, planning a business trip to Seattle can be exciting. Some key points to consider for your trip:\n",
|
||||
"\n",
|
||||
"1. **Weather**: Check the weather forecast for Seattle to pack appropriately. Seattle is known for its mild, marine climate with possible rainfall throughout the year.\n",
|
||||
"\n",
|
||||
"2. **Transportation**: Familiarize yourself with transportation options like Sea-Tac Airport, light rail, buses, and ride-sharing services for getting around.\n",
|
||||
"\n",
|
||||
"3. **Accommodation**: Book a hotel near your meeting location or in a convenient area like Downtown Seattle for easy access to amenities.\n",
|
||||
"\n",
|
||||
"4. **Meetings**: Confirm all meeting schedules and locations in advance. Consider any time differences if coming from outside the Pacific Time Zone.\n",
|
||||
"\n",
|
||||
"5. **Business Etiquette**: Brush up on general business etiquette, especially if you're meeting with new clients or partners.\n",
|
||||
"\n",
|
||||
"6. **Networking**: Look out for industry events or networking opportunities during your stay.\n",
|
||||
"\n",
|
||||
"7. **Dining**: Research dining options for client meetings or personal meals, Seattle offers a diverse culinary scene.\n",
|
||||
"\n",
|
||||
"8. **Leisure**: If time permits, plan to explore local attractions like Pike Place Market, Space Needle, or take a stroll along Puget Sound.\n",
|
||||
"\n",
|
||||
"Safe travels and have a productive trip!\n",
|
||||
"Response 10: \n",
|
||||
"That sounds like a nice plan! Catching up with Liam over a meal after his return from Google in New York will likely be an opportunity to exchange experiences and stories. Here are a few tips for your meet-up:\n",
|
||||
"\n",
|
||||
"1. **Choose a Venue**: Pick a restaurant that suits both your tastes and preferences, perhaps somewhere central or with a special significance to your friendship.\n",
|
||||
"\n",
|
||||
"2. **Schedule**: Coordinate a date and time that works well for both of your schedules, considering Liam might need time to adjust after his return.\n",
|
||||
"\n",
|
||||
"3. **Conversation Topics**: Prepare some conversation starters about his experience at Google, life in New York, and any shared interests or memories from your time in class together.\n",
|
||||
"\n",
|
||||
"4. **Professional Insights**: Liam might have valuable insights from his work at Google that could be beneficial for your own career growth, especially considering your interest in the tech industry.\n",
|
||||
"\n",
|
||||
"5. **Personal Updates**: Share your own updates too – your work at Meta, your love for watermelon, and your piano hobby could spark interesting discussions.\n",
|
||||
"\n",
|
||||
"Enjoy your reunion and the chance to learn from each other's experiences!\n",
|
||||
"Response 11: \n",
|
||||
"As Cynthia hugged her tightly, User felt a bittersweet pang. \"Promise we'll stay close,\" User whispered, staring at the luggage adorned with University of Iowa stickers. Cynthia smiled, eyes glistening, \"Distance means nothing when hearts are intertwined.\" They laughed, remembering late-night study sessions fueled by watermelon slices, and the echoes of piano melodies that filled User's apartment. As the cab honked, their hands reluctantly parted, but their dreams intertwined, ready for chapters anew.\n",
|
||||
"Response 12: \n",
|
||||
"I'm unable to provide real-time information or look up specific companies without prior data. If SMCI is a hypothetical or fictional company within our conversation context, I don't have details on it. For actual companies, it would be best to search online or refer to official sources for the most accurate and up-to-date information on their nature and activities.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"What are the prospects like if I go to work at Amazon?\")\n",
|
||||
"print(\"Response 8: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Note: I am planning a business trip to Seattle next week.\")\n",
|
||||
"print(\"Response 9: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"My classmate Liam is currently working at Google. He will return to New York next month, and I plan to have a meal with him.\")\n",
|
||||
"print(\"Response 10: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Cynthia is my best friend, and she has decided to go to university in Iowa. Write an 80-word micro drama starting with this.\")\n",
|
||||
"print(\"Response 11: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"What kind of company is SMCI, and what do they do?\")\n",
|
||||
"print(\"Response 12: \\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: user is interested in career prospects at amazon (valid)\n",
|
||||
"new observation: user is inquiring about smci's nature and business (valid)\n",
|
||||
"new observation: user is planning a business trip to seattle next week (Inference time: next week after august 2, 2024) (valid)\n",
|
||||
"new observation: user plans to meet classmate liam in new york next month (Inference time: next month after august 2, 2024) (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"We can see **Memory Consolidation** successfully filtered out fictitious contents, and shows good time sensitivity.\n",
|
||||
"\n",
|
||||
"We try more cases to test its resolution of conflicting contents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Response 13: \n",
|
||||
"You might enjoy eating watermelon this afternoon since you like it.\n",
|
||||
"Response 14: \n",
|
||||
"Great choice! Enjoy your mangoes today.\n",
|
||||
"Response 15: \n",
|
||||
"Congratulations on your new job at Apple! How are you finding the experience so far?\n",
|
||||
"Response 16: \n",
|
||||
"That's wonderful! Peaches and apples are both delicious and nutritious choices. Enjoy snacking on them whenever you like.\n",
|
||||
"Response 17: \n",
|
||||
"No problem at all, everyone has their preferences. If you don't like coconuts, there are plenty of other fruits to enjoy.\n",
|
||||
"Response 18: \n",
|
||||
"Sounds like an exciting plan! Florida offers great surfing spots with its warm waters and waves. Have a fantastic time surfing next month.\n",
|
||||
"Response 19:\n",
|
||||
"Happy Birthday for tomorrow! May your day be filled with joy, laughter, and memorable moments.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"What fruit should I eat this afternoon?\")\n",
|
||||
"print(\"Response 13: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Watermelon is really good, but I also like mangoes. Today, I want to eat mangoes.\")\n",
|
||||
"print(\"Response 14: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"I recently switched jobs and joined Apple.\")\n",
|
||||
"print(\"Response 15: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"I also like eating peaches and apples.\")\n",
|
||||
"print(\"Response 16: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"I don't like eating coconuts.\")\n",
|
||||
"print(\"Response 17: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"I am planning to go surfing in Florida next month.\")\n",
|
||||
"print(\"Response 18: \\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Tomorrow is my birthday\")\n",
|
||||
"print(\"Response 19:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: user likes mangoes and wants to eat them today (valid)\n",
|
||||
"new observation: user is planning to go surfing in florida next month (valid)\n",
|
||||
"new observation: user's birthday is tomorrow (valid)\n",
|
||||
"new observation: user recently switched jobs and joined apple (valid)\n",
|
||||
"new observation: user likes eating peaches and apples (valid)\n",
|
||||
"new observation: user doesn't like eating coconuts (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Reflection and Re-Consolidation\n",
|
||||
"Now, we have accumulated enough new *observations* in the system, so we can call **Reflection and Re-Consolidation**, let's see what will it get."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new insight: The Travel plans of User: travel plans include a business trip to seattle next week after august 2, 2024, meeting a classmate in new york next month after august 2, 2024, and going surfing in florida next month (valid)\n",
|
||||
"new insight: The Fruit preferences of User: fruit preferences: watermelon, peaches, apples, mangoes; dislikes coconuts (valid)\n",
|
||||
"new insight: The Career interest of User: amazon (valid)\n",
|
||||
"modified observation: user is planning a business trip to seattle next week (Inference time: next week after august 2, 2024) (valid)\n",
|
||||
"modified observation: user likes mangoes and wants to eat them today (valid)\n",
|
||||
"modified observation: user is interested in career prospects at amazon (valid)\n",
|
||||
"modified observation: user is inquiring about smci's nature and business (valid)\n",
|
||||
"modified observation: user recently switched jobs and joined apple (valid)\n",
|
||||
"modified observation: user's hobby is playing the piano (valid)\n",
|
||||
"modified observation: user is planning to go surfing in florida next month (valid)\n",
|
||||
"modified observation: user's birthday is tomorrow (valid)\n",
|
||||
"modified observation: user likes watermelon (valid)\n",
|
||||
"modified observation: user works for meta (valid)\n",
|
||||
"modified observation: user plans to meet classmate liam in new york next month (Inference time: next month after august 2, 2024) (valid)\n",
|
||||
"modified observation: user likes eating peaches and apples (valid)\n",
|
||||
"modified observation: user doesn't like eating coconuts (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.reflect_and_reconsolidate()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Low response-time (RT) for the user\n",
|
||||
"Finally, we test the RT of MemoryScope system for the user. Specifically, we test the difference of RT when responding with and without retrieving memory pieces from the system."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I play any musical instruments?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"With memory retrieval\\nResponse 20: \\n\" + response.message.content + f\"\\n RT: {total_time} seconds\\n\")\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I have any plans in the next month?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"With memory retrieval\\nResponse 21: \\n\" + response.message.content + f\"\\n RT: {total_time} seconds\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"With memory retrieval\n",
|
||||
"Response 20: \n",
|
||||
"Yes, you play the piano.\n",
|
||||
" RT: 1.494797706604004 seconds\n",
|
||||
"\n",
|
||||
"With memory retrieval\n",
|
||||
"Response 21: \n",
|
||||
"Yes, you have the following plans in the next month:\n",
|
||||
"\n",
|
||||
"1. A business trip to Seattle sometime after August 2, 2024.\n",
|
||||
"2. Meeting a classmate in New York next month, also after August 2, 2024.\n",
|
||||
"3. Going surfing in Florida next month.\n",
|
||||
" RT: 6.400442123413086 seconds\n",
|
||||
"\n",
|
||||
"Without memory retrieval\n",
|
||||
"Response 22: \n",
|
||||
"I'm sorry, but as MemoryScope, I don't have access to personal information about individuals unless it has been shared with me during our conversation. Therefore, I cannot determine if you play any musical instruments without you providing that information. Have you mentioned anything about your musical abilities before?\n",
|
||||
" RT: 3.8412117958068848 seconds\n",
|
||||
"\n",
|
||||
"Without memory retrieval\n",
|
||||
"Response 23: \n",
|
||||
"I'm sorry, but as an AI, I don't have access to personal schedules or information unless it has been shared with me during our conversation, which we haven't had. I cannot access external databases or personal calendars. I'm here to provide general information and assistance based on the knowledge I've been trained on.\n",
|
||||
" RT: 6.7565529346466064 seconds\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_chat.run_service_operation(\"delete_all\")\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I play any musical instruments?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"Without memory retrieval\\nResponse 22: \\n\" + response.message.content + f\"\\n RT: {total_time} seconds\\n\")\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"Do you know if I have any plans in the next month?\\n\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"Without memory retrieval\\nResponse 23: \\n\" + response.message.content + f\"\\n RT: {total_time} seconds\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"We can see responding with retrieving memory pieces from MemoryScope does not increase RT."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## More Examples\n",
|
||||
"We direct the reader to [Advanced Customization](../advance/custom_operator.md) for guidance on customizing the various settings of the MemoryScope system. It is also possible to create or customize your own MemoryScope **operations** by specifying a **workflow** and the corresponding **workers** that best meet your specific needs.\n",
|
||||
"\n",
|
||||
"Additionally, you can also try out the [CLI with a MemoryScope Chatbot](../cli/CLI_README.md). We have implemented the chatbot so that the **Memory Consolidation** and **Reflection and Re-Consolidation** operations are always run asynchronously in the backend, ensuring that they do not incur any response time for the user.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
|
|
@ -1,488 +0,0 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# chat 和 service 接口的示例用法\n",
|
||||
"这个笔记本展示了 MemoryScope 的 **chat** 和 **service** 接口的简单用法,以及它的主要功能。\n",
|
||||
"\n",
|
||||
"在运行这个笔记本之前,请先按照 Readme 中的 [**Installation**](../../docs/installation_zh.md#三通过-pypi-安装) 指南进行安装,并启动 Docker 镜像。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 初始化一个 MemoryScope 实例\n",
|
||||
"首先,我们需要指定一个配置并初始化一个 MemoryScope 实例。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from memoryscope import MemoryScope, Arguments\n",
|
||||
"arguments = Arguments(\n",
|
||||
" language=\"cn\",\n",
|
||||
" human_name=\"用户\",\n",
|
||||
" assistant_name=\"AI\",\n",
|
||||
" memory_chat_class=\"api_memory_chat\",\n",
|
||||
" generation_backend=\"dashscope_generation\",\n",
|
||||
" generation_model=\"qwen-max\",\n",
|
||||
" embedding_backend=\"dashscope_embedding\",\n",
|
||||
" embedding_model=\"text-embedding-v2\",\n",
|
||||
" rank_backend=\"dashscope_rank\",\n",
|
||||
" rank_model=\"gte-rerank\",\n",
|
||||
" enable_ranker=True)\n",
|
||||
"\n",
|
||||
"ms = MemoryScope(arguments=arguments)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 聊天(不含记忆)\n",
|
||||
"MemoryScope 配有默认的 chat 接口,因此开始聊天非常容易,就像使用任何大型语言模型聊天机器人一样。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"回答1:\n",
|
||||
"很高兴了解到您的爱好是弹琴,这是一种既能陶冶情操又能提升音乐技能的美妙艺术形式。无论是古典钢琴、爵士乐还是现代流行曲目,每一种风格都能带来不同的享受和挑战。希望您在弹琴的过程中能够持续发现乐趣,创造出更多动人的旋律。\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_chat = ms.default_memory_chat\n",
|
||||
"memory_chat.run_service_operation(\"delete_all\")\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我的爱好是弹琴。\")\n",
|
||||
"print(\"回答1:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"你可以选择进行含有或不含有多轮对话上下文的聊天。然而,由于尚未调用**记忆巩固**功能,系统中还没有任何记忆片段。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"回答2:\n",
|
||||
"是的,您提到过您的爱好是弹琴,所以我认为您对键盘乐器,特别是钢琴有一定的爱好。\n",
|
||||
"回答3:\n",
|
||||
"作为基于当前对话的MemoryScope智能助理,我没有之前关于您乐器爱好的信息。请告诉我,您喜欢哪种乐器?这样我就可以记住并提供相关帮助了。\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我有什么乐器爱好吗?\")\n",
|
||||
"print(\"回答2:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我有什么乐器爱好吗?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"print(\"回答3:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## **记忆巩固**\n",
|
||||
"现在,我们再聊多几句,然后尝试**记忆巩固**功能。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"回答4:\n",
|
||||
"了解,您在阿里巴巴工作。阿里巴巴集团是一家总部位于中国杭州的全球领先的电子商务和科技公司,以其电子商务平台如淘宝、天猫闻名,同时在云计算、数字媒体及娱乐、金融科技等领域也有广泛布局。如果您有关于工作、技术或公司文化方面的问题,欢迎随时询问。\n",
|
||||
"回答5:\n",
|
||||
"选择水果可以根据个人口味、营养需求以及季节来决定。夏天,一些清爽解暑的水果会是不错的选择,比如西瓜、哈密瓜、葡萄、桃子或者李子,它们都含有丰富的水分和维生素,有助于消暑降温。如果您想要补充纤维素,火龙果或者猕猴桃也是很好的选择。最终,选择哪种水果,还是要看您自己的喜好和身体状况。\n",
|
||||
"回答6:\n",
|
||||
"西瓜是夏季的理想选择,它不仅清凉解渴,还含有大量的水分和电解质,可以帮助身体补充流失的水分。西瓜还富含维生素C、A和抗氧化剂,如番茄红素,对皮肤健康和心血管系统都有益处。享用美味的西瓜时,记得切块后冷藏一下,口感会更加清爽哦!\n",
|
||||
"回答7:\n",
|
||||
"\"生日快乐,愿你的每一天都如蛋糕般甜蜜,笑容比烛光更灿烂!\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"我在阿里巴巴干活\")\n",
|
||||
"print(\"回答4:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"今天下午吃什么水果好?\")\n",
|
||||
"print(\"回答5:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我喜欢吃西瓜。\")\n",
|
||||
"print(\"回答6:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"帮我写一句给朋友的生日祝福语,简短一点。\")\n",
|
||||
"print(\"回答7:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: 用户爱好弹琴。 (valid)\n",
|
||||
"new observation: 用户在阿里巴巴工作。 (valid)\n",
|
||||
"new observation: 用户喜欢吃西瓜。 (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_service = ms.default_memory_service\n",
|
||||
"memory_service.init_service()\n",
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"**记忆巩固**从用户的7条聊天消息中提取了3条 *observations* ,其余无效的信息被过滤掉了。\n",
|
||||
"\n",
|
||||
"我们尝试更多的情况,以测试其时间感知能力和过滤用户虚构的内容的能力。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"回答8:\n",
|
||||
"京东作为中国领先的电商平台之一,拥有广泛的业务范围和良好的行业声誉。加入京东工作,您可能会享受到以下几方面的前景优势:\n",
|
||||
"\n",
|
||||
"1. **职业发展**:京东提供多元化的职业路径和晋升机会,您可以在电商、物流、科技、金融等多个领域找到适合自己的位置和发展空间。\n",
|
||||
"\n",
|
||||
"2. **技术与创新**:京东持续投入于技术创新,尤其是在人工智能、大数据、云计算等方面,为员工提供了接触和学习前沿技术的机会。\n",
|
||||
"\n",
|
||||
"3. **企业文化**:京东强调“正道成功”的企业文化,注重诚信、团队合作与社会责任,有利于塑造积极向上的工作氛围。\n",
|
||||
"\n",
|
||||
"4. **福利待遇**:京东通常为员工提供有竞争力的薪酬福利体系,包括但不限于健康保险、员工培训、股权激励等。\n",
|
||||
"\n",
|
||||
"5. **行业影响力**:作为行业巨头,京东的从业经历对您的职业生涯将是一大亮点,有助于提升个人品牌和未来的职业选择灵活性。\n",
|
||||
"\n",
|
||||
"当然,具体前景还取决于您的职位、个人能力、行业趋势及个人职业规划等因素。建议您深入了解目标岗位的具体要求,评估个人与岗位的匹配度,并关注行业动态,为自己的职业发展做出合理规划。\n",
|
||||
"回答9:\n",
|
||||
"好的,已记录您计划下周去北京出差。请随时告诉我如果您需要关于北京的出行建议、天气预报或是其他相关信息。祝您出差顺利!\n",
|
||||
"回答10:\n",
|
||||
"已记录您的安排,您将于下个月与在亚马逊工作的同学李亚平在上海共进晚餐。临近日期时,不妨提前联系李亚平确认具体时间和地点,以便愉快地进行聚会。希望你们有个美好的重聚!\n",
|
||||
"回答11:\n",
|
||||
"【场景:傍晚,公园长椅】 \n",
|
||||
"小亮是我最好的朋友,他决定去山西上大学。夕阳下,我们肩并肩坐着。 \n",
|
||||
"我:“山西的面食可出名了,你这小吃货有福了!” \n",
|
||||
"小亮笑:“那必须的,说好你放假就来找我,咱们一起吃遍山西!” \n",
|
||||
"我点头,心中泛起不舍:“一言为定,别忘了,那里还有千年古城等你探索。” \n",
|
||||
"小亮看向远方,眼里闪烁着梦想的光:“新旅程,我们一起加油!” \n",
|
||||
"【画面渐暗,友情的力量温暖而坚定】\n",
|
||||
"回答12:\n",
|
||||
"SMCI可能指代的是Super Micro Computer, Inc.(超微电脑股份有限公司),简称Supermicro。这是一家总部位于美国加利福尼亚州圣何塞的公司,成立于1993年。Supermicro主要设计、制造和销售高性能服务器和技术解决方案,包括服务器、存储系统、主板以及支持云计算、数据中心、企业IT、高性能计算(HPC)和嵌入式系统的其他硬件组件。它们的产品以高效率、灵活性和定制化选项著称,在全球范围内服务于各种规模的企业和组织。\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"假如我去京东工作,前景怎么样?\")\n",
|
||||
"print(\"回答8:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"记一下,下周我准备去北京出差\")\n",
|
||||
"print(\"回答9:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我同学李亚平现在在亚马逊工作,他下个月回上海,我要和他吃个饭\")\n",
|
||||
"print(\"回答10:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"小亮是我最好的朋友,他决定去山西上大学。以这个为开头写一个80字的微剧本。\")\n",
|
||||
"print(\"回答11:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"SMCI是什么公司,做什么的?\")\n",
|
||||
"print(\"回答12:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: 用户计划2024年8月9日去北京出差。 (推断时间: 2024年8月9日) (valid)\n",
|
||||
"new observation: 用户的同学李亚平下个月回上海,用户将与其见面吃饭。 (推断时间: 2024年9月) (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"我们可以看到,**记忆巩固**成功过滤掉了虚假内容,并展示了良好的时间敏感性。\n",
|
||||
"\n",
|
||||
"我们尝试更多的情况,以测试其解决冲突内容的能力。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"回答13:\n",
|
||||
"既然您喜欢吃西瓜,今天下午吃西瓜是个不错的选择。\n",
|
||||
"回答14:\n",
|
||||
"那太好了,如果您今天想换换口味,吃芒果也是很好的选择,享受它的香甜吧!\n",
|
||||
"回答15:\n",
|
||||
"恭喜您加入美团!希望您的新工作一切顺利,有新的挑战和机遇。\n",
|
||||
"回答16:\n",
|
||||
"了解了,桃子和苹果都是既营养又美味的选择,多吃水果对身体有益,您可以根据季节和个人喜好来挑选。\n",
|
||||
"回答17:\n",
|
||||
"好的,知道您不喜欢椰子,以后在推荐水果时会留意这一点。\n",
|
||||
"回答18:\n",
|
||||
"听起来很有趣!去海南冲浪是个很棒的计划,下个月那边的天气应该很适合水上活动,祝您玩得开心!别忘了做好防晒哦。\n",
|
||||
"回答19:\n",
|
||||
"生日快乐!希望您明天能度过一个特别且难忘的一天,满满的祝福给您!有任何庆祝计划吗?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = memory_chat.chat_with_memory(query=\"今天下午吃什么水果好?\")\n",
|
||||
"print(\"回答13:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"西瓜确实不错,但是我也喜欢吃芒果。我今天想吃芒果。\")\n",
|
||||
"print(\"回答14:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我最近跳槽去了美团。\")\n",
|
||||
"print(\"回答15:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我还喜欢吃桃子和苹果。\")\n",
|
||||
"print(\"回答16:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我不喜欢吃椰子。\")\n",
|
||||
"print(\"回答17:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"我准备下个月去海南冲浪。\")\n",
|
||||
"print(\"回答18:\\n\" + response.message.content)\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"明天是我生日。\")\n",
|
||||
"print(\"回答19:\\n\" + response.message.content)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new observation: 用户喜欢吃桃子和苹果。 (valid)\n",
|
||||
"new observation: 用户不喜欢吃椰子。 (valid)\n",
|
||||
"new observation: 用户喜欢吃芒果。 (valid)\n",
|
||||
"new observation: 用户计划2024年9月去海南冲浪。 (推断时间: 2024年9月) (valid)\n",
|
||||
"new observation: 用户的生日是每年8月3日。 (推断时间: 每年8月3日) (valid)\n",
|
||||
"modified observation: 用户在阿里巴巴工作。 (expired)\n",
|
||||
"modified observation: 用户最近跳槽至美团。 (expired)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.consolidate_memory()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## **反思与再巩固**\n",
|
||||
"现在,我们在系统中已经积累了足够多的新的 *observations* ,因此我们可以调用**反思与再巩固**功能,让我们看看会得到什么。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"consolidate_memory result=[MEMORY ACTIONS]:\n",
|
||||
"new insight: 用户的出差计划: 2024年8月9日去北京出差 (valid)\n",
|
||||
"new insight: 用户的生日: 每年8月3日 (valid)\n",
|
||||
"new insight: 用户的水果偏好: 喜欢桃子、苹果、西瓜、芒果,不喜欢吃椰子 (valid)\n",
|
||||
"modified observation: 用户计划2024年8月9日去北京出差。 (推断时间: 2024年8月9日) (valid)\n",
|
||||
"modified observation: 用户的生日是每年8月3日。 (推断时间: 每年8月3日) (valid)\n",
|
||||
"modified observation: 用户计划2024年9月去海南冲浪。 (推断时间: 2024年9月) (valid)\n",
|
||||
"modified observation: 用户喜欢吃芒果。 (valid)\n",
|
||||
"modified observation: 用户喜欢吃桃子和苹果。 (valid)\n",
|
||||
"modified observation: 用户爱好弹琴。 (valid)\n",
|
||||
"modified observation: 用户喜欢吃西瓜。 (valid)\n",
|
||||
"modified observation: 用户不喜欢吃椰子。 (valid)\n",
|
||||
"modified observation: 用户的同学李亚平下个月回上海,用户将与其见面吃饭。 (推断时间: 2024年9月) (valid)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"result = memory_service.reflect_and_reconsolidate()\n",
|
||||
"print(f\"consolidate_memory result={result}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 低用户时延(RT)\n",
|
||||
"\n",
|
||||
"最后,我们测试 MemoryScope 系统对用户的响应时间 (RT)。具体来说,我们测试在有和没有从系统中检索记忆片段时聊天的响应时间的差异。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我的乐器爱好是什么吗?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"使用记忆检索\\n回答20:\\n\" + response.message.content + f\"\\n 耗时:{total_time}秒\\n\")\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我接下去的一个月内有什么计划吗?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"使用记忆检索\\n回答21:\\n\" + response.message.content + f\"\\n 耗时:{total_time}秒\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"使用记忆检索\n",
|
||||
"回答20:\n",
|
||||
"您喜欢弹琴。\n",
|
||||
" 耗时:1.3783161640167236秒\n",
|
||||
"\n",
|
||||
"使用记忆检索\n",
|
||||
"回答21:\n",
|
||||
"您接下来一个月内的计划包括:\n",
|
||||
"- 2024年8月9日去北京出差。\n",
|
||||
"- 计划在2024年9月去海南冲浪。\n",
|
||||
"- 2024年9月,您的同学李亚平回上海,您将与他见面吃饭。\n",
|
||||
" 耗时:6.538439035415649秒\n",
|
||||
"\n",
|
||||
"不使用记忆检索\n",
|
||||
"回答20:\n",
|
||||
"对不起,我没有记录您的个人信息,包括您的乐器爱好。如果您告诉我,我可以帮您记住。\n",
|
||||
" 耗时:2.597784996032715秒\n",
|
||||
"\n",
|
||||
"不使用记忆检索\n",
|
||||
"回答21:\n",
|
||||
"对不起,作为基于当前会话的MemoryScope智能助理,我无法获取或存储您的个人日程信息。如果您需要查询自己的计划,建议您查看自己的日历或者备忘录。\n",
|
||||
" 耗时:5.246160984039307秒\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"memory_chat.run_service_operation(\"delete_all\")\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我的乐器爱好是什么吗?\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"不使用记忆检索\\n回答20:\\n\" + response.message.content + f\"\\n 耗时:{total_time}秒\\n\")\n",
|
||||
"\n",
|
||||
"start_time = time.time()\n",
|
||||
"response = memory_chat.chat_with_memory(query=\"你知道我接下去的一个月内有什么计划吗?\\n\",\n",
|
||||
" history_message_strategy=None)\n",
|
||||
"end_time = time.time()\n",
|
||||
"total_time = end_time - start_time\n",
|
||||
"print(\"不使用记忆检索\\n回答21:\\n\" + response.message.content + f\"\\n 耗时:{total_time}秒\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"----\n",
|
||||
"我们可以看到,从 MemoryScope 检索记忆片段不会增加聊天的响应时间。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 更多用法\n",
|
||||
"我们建议读者参考[进阶自定义用法](../advance/custom_operator_zh.md)来对MemoryScope系统进行各种自定义设置。您还可以通过自定义**workflow**和对应的**worker**来创建或定制满足您特定需求的**operation**。\n",
|
||||
"\n",
|
||||
"此外,您还可以尝试使用[在命令行与MemoryScope聊天机器人交互](../cli/CLI_README_ZH.md)。我们在这里实现了始终在后台异步运行**记忆巩固**和**反思与再巩固**这两个操作,从而使得它们不会增加聊天的响应时间。"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# The Cli Interface of MemoryScope
|
||||
|
||||
## Usage
|
||||
Before running, follow the [**Installation**](../../docs/installation.md#iii-install-from-pypi) guidelines in Readme, and start the Docker image first.
|
||||
MemoryScope can be launched in two different ways:
|
||||
|
||||
### 1. Using YAML Configuration File
|
||||
|
||||
If you prefer to configure your settings via a YAML file, you can do so by providing the path to the configuration file as follows:
|
||||
```bash
|
||||
memoryscope --config_path=memoryscope/core/config/demo_config.yaml
|
||||
```
|
||||
|
||||
### 2. Using Command Line Arguments
|
||||
|
||||
Alternatively, you can specify all the parameters directly on the command line:
|
||||
|
||||
```bash
|
||||
# Chinese / Dashscope
|
||||
memoryscope --language="cn" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="用户" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="dashscope_generation" \
|
||||
--generation_model="qwen-max" \
|
||||
--embedding_backend="dashscope_embedding" \
|
||||
--embedding_model="text-embedding-v2" \
|
||||
--enable_ranker=True \
|
||||
--rank_backend="dashscope_rank" \
|
||||
--rank_model="gte-rerank"
|
||||
# English / OpenAI
|
||||
memoryscope --language="en" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="user" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="openai_generation" \
|
||||
--generation_model="gpt-4o" \
|
||||
--embedding_backend="openai_embedding" \
|
||||
--embedding_model="text-embedding-3-small" \
|
||||
--enable_ranker=False
|
||||
```
|
||||
|
||||
Here are the available options that can be set through either method:
|
||||
|
||||
- `--language`: The language used for the conversation.
|
||||
- `--memory_chat_class`: The class name for managing the chat history.
|
||||
- `--human_name`: The name of the human user.
|
||||
- `--assistant_name`: The name of the AI assistant.
|
||||
- `--generation_backend`: The backend used for generating responses.
|
||||
- `--generation_model`: The model used for generating responses.
|
||||
- `--embedding_backend`: The backend used for text embeddings.
|
||||
- `--embedding_model`: The model used for creating text embeddings.
|
||||
- `--enable_ranker`: A boolean indicating whether to use a dummy ranker (default is `False`).
|
||||
- `--rank_backend`: The backend used for ranking responses.
|
||||
- `--rank_model`: The model used for ranking responses.
|
||||
|
||||
### 3. View Memory
|
||||
You can open two command line windows following the method in the second step.
|
||||
In one command line window, you can have a conversation with the AI, while in the other, you can check the AI's long-term memory about the user.
|
||||
Use /help to open the command line help, and find the command /list_memory along with the corresponding auto-refresh instruction.
|
||||
```
|
||||
/list_memory refresh_time=5
|
||||
```
|
||||
Then you can enjoy a pleasant conversation with the AI!
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# MemoryScope 的命令行接口
|
||||
|
||||
## 使用方法
|
||||
在运行之前,请先按照 Readme 中的 [**Installation**](../../docs/installation_zh.md#三通过-pypi-安装) 指南进行安装,并启动 Docker 镜像。
|
||||
MemoryScope 可以通过两种不同的方式启动:
|
||||
|
||||
### 1. 使用 YAML 配置文件
|
||||
|
||||
如果您更喜欢通过 YAML 文件配置设置,可以通过提供配置文件的路径来实现:
|
||||
```bash
|
||||
memoryscope --config_path=memoryscope/core/config/demo_config_zh.yaml
|
||||
```
|
||||
|
||||
### 2. 使用命令行参数
|
||||
|
||||
或者,您可以直接在命令行上指定所有参数:
|
||||
|
||||
```
|
||||
# 中文
|
||||
memoryscope --language="cn" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="用户" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="dashscope_generation" \
|
||||
--generation_model="qwen-max" \
|
||||
--embedding_backend="dashscope_embedding" \
|
||||
--embedding_model="text-embedding-v2" \
|
||||
--enable_ranker=True \
|
||||
--rank_backend="dashscope_rank" \
|
||||
--rank_model="gte-rerank"
|
||||
# 英文
|
||||
memoryscope --language="en" \
|
||||
--memory_chat_class="cli_memory_chat" \
|
||||
--human_name="User" \
|
||||
--assistant_name="AI" \
|
||||
--generation_backend="openai_generation" \
|
||||
--generation_model="gpt-4o" \
|
||||
--embedding_backend="openai_embedding" \
|
||||
--embedding_model="text-embedding-3-small" \
|
||||
--enable_ranker=False
|
||||
```
|
||||
|
||||
以下是可以通过任一方法设置的可用选项:
|
||||
|
||||
- `--language`: 对话中使用的语言。
|
||||
- `--memory_chat_class`: 管理聊天记录的类名。
|
||||
- `--human_name`: 人类用户的名字。
|
||||
- `--assistant_name`: AI 助手的名字。
|
||||
- `--generation_backend`: 用于生成回复的后端。
|
||||
- `--generation_model`: 用于生成回复的模型。
|
||||
- `--embedding_backend`: 用于文本嵌入的后端。
|
||||
- `--embedding_model`: 用于创建文本嵌入的模型。
|
||||
- `--enable_ranker`: 一个布尔值,指示是否使用排名器(默认为 False)。
|
||||
- `--rank_backend`: 用于排名回复的后端。
|
||||
- `--rank_model`: 用于排名回复的模型。
|
||||
|
||||
### 3. 查看记忆
|
||||
按照第二步的方式可以打开两个命令行的窗口。
|
||||
其中一个命令行窗口可以和AI进行对话,另一个命令行窗口可以查看AI关于用户的长期记忆
|
||||
使用/help打开命令行帮助,找到/list_memory的命令和对应自动刷新的指令。
|
||||
```
|
||||
/list_memory refresh_time=5
|
||||
```
|
||||
接下来就可以和AI进行愉快地交流啦。
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
sh examples/docker/run_elastic_search.sh
|
||||
python quick-start-demo.py --config_path=memoryscope/core/config/demo_config_zh.yaml
|
||||
|
|
@ -1 +0,0 @@
|
|||
su - elastic_search_user -c "/home/elastic_search_user/elastic_search/elasticsearch-8.15.0/bin/elasticsearch -E xpack.security.enabled=false -E discovery.type=single-node -E xpack.license.self_generated.type=trial -d"
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
""" Version of MemoryScope."""
|
||||
__version__ = "0.1.1.0"
|
||||
import fire
|
||||
|
||||
from memoryscope.core.config.arguments import Arguments # noqa: F401
|
||||
from memoryscope.core.memoryscope import MemoryScope # noqa: F401
|
||||
|
||||
|
||||
def cli():
|
||||
fire.Fire(MemoryScope.cli_memory_chat)
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from . import common_constants
|
||||
from . import language_constants
|
||||
|
||||
|
||||
__all__ = [
|
||||
"common_constants",
|
||||
"language_constants"
|
||||
]
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# common_constants.py
|
||||
# This module defines constants used as keys throughout the application to maintain a consistent reference
|
||||
# for data structures related to workflow management, chat interactions, context storage, memory operations,
|
||||
# node processing, and temporal inference functionalities.
|
||||
|
||||
WORKFLOW_NAME = "workflow_name"
|
||||
|
||||
MEMORYSCOPE_CONTEXT = "memoryscope_context"
|
||||
|
||||
RESULT = "result"
|
||||
|
||||
MEMORIES = "memories"
|
||||
|
||||
CHAT_MESSAGES = "chat_messages"
|
||||
|
||||
CHAT_MESSAGES_SCATTER = "chat_messages_scatter"
|
||||
|
||||
CHAT_KWARGS = "chat_kwargs"
|
||||
|
||||
USER_NAME = "user_name"
|
||||
|
||||
TARGET_NAME = "target_name"
|
||||
|
||||
MEMORY_MANAGER = "memory_manager"
|
||||
|
||||
QUERY_WITH_TS = "query_with_ts"
|
||||
|
||||
RETRIEVE_MEMORY_NODES = "retrieve_memory_nodes"
|
||||
|
||||
RANKED_MEMORY_NODES = "ranked_memory_nodes"
|
||||
|
||||
NOT_REFLECTED_NODES = "not_reflected_nodes"
|
||||
|
||||
NOT_UPDATED_NODES = "not_updated_nodes"
|
||||
|
||||
EXTRACT_TIME_DICT = "extract_time_dict"
|
||||
|
||||
NEW_OBS_NODES = "new_obs_nodes"
|
||||
|
||||
NEW_OBS_WITH_TIME_NODES = "new_obs_with_time_nodes"
|
||||
|
||||
INSIGHT_NODES = "insight_nodes"
|
||||
|
||||
TODAY_NODES = "today_nodes"
|
||||
|
||||
MERGE_OBS_NODES = "merge_obs_nodes"
|
||||
|
||||
TIME_INFER = "time_infer"
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
from memoryscope.enumeration.language_enum import LanguageEnum
|
||||
|
||||
# This dictionary maps languages to lists of words related to datetime expressions.
|
||||
# It aids in recognizing and processing datetime mentions in text, enhancing the system's ability to understand
|
||||
# temporal context across different languages.
|
||||
DATATIME_WORD_LIST = {
|
||||
LanguageEnum.CN: [
|
||||
"天",
|
||||
"周",
|
||||
"月",
|
||||
"年",
|
||||
"星期",
|
||||
"点",
|
||||
"分钟",
|
||||
"小时",
|
||||
"秒",
|
||||
"上午",
|
||||
"下午",
|
||||
"早上",
|
||||
"早晨",
|
||||
"晚上",
|
||||
"中午",
|
||||
"日",
|
||||
"夜",
|
||||
"清晨",
|
||||
"傍晚",
|
||||
"凌晨",
|
||||
"岁",
|
||||
],
|
||||
LanguageEnum.EN: [
|
||||
# Units of Time
|
||||
"year", "yr",
|
||||
"month", "mo",
|
||||
"week", "wk",
|
||||
"day", "d",
|
||||
"hour", "hr",
|
||||
"minute", "min",
|
||||
"second", "sec",
|
||||
|
||||
# Days of the Week
|
||||
"Monday", "Mon",
|
||||
"Tuesday", "Tue", "Tues",
|
||||
"Wednesday", "Wed",
|
||||
"Thursday", "Thu", "Thur", "Thurs",
|
||||
"Friday", "Fri",
|
||||
"Saturday", "Sat",
|
||||
"Sunday", "Sun",
|
||||
|
||||
# Months of the Year
|
||||
"January", "Jan",
|
||||
"February", "Feb",
|
||||
"March", "Mar",
|
||||
"April", "Apr",
|
||||
"May", "May",
|
||||
"June", "Jun",
|
||||
"July", "Jul",
|
||||
"August", "Aug",
|
||||
"September", "Sep", "Sept",
|
||||
"October", "Oct",
|
||||
"November", "Nov",
|
||||
"December", "Dec",
|
||||
|
||||
# Relative Time References
|
||||
"Today",
|
||||
"Tomorrow", "Tmrw",
|
||||
"Yesterday", "Yday",
|
||||
"Now",
|
||||
"Morning", "AM", "a.m.",
|
||||
"Afternoon", "PM", "p.m.",
|
||||
"Evening",
|
||||
"Night",
|
||||
"Midnight",
|
||||
"Noon",
|
||||
|
||||
# Seasonal References
|
||||
"Spring",
|
||||
"Summer",
|
||||
"Autumn", "Fall",
|
||||
"Winter",
|
||||
|
||||
# General Time References
|
||||
"Century", "cent.",
|
||||
"Decade",
|
||||
"Millennium",
|
||||
"Quarter", "Q1", "Q2", "Q3", "Q4",
|
||||
"Semester",
|
||||
"Fortnight",
|
||||
"Weekend"
|
||||
]
|
||||
}
|
||||
|
||||
# A mapping of weekdays for each supported language, facilitating calendar-related operations and understanding
|
||||
# within the application.
|
||||
WEEKDAYS = {
|
||||
LanguageEnum.CN: [
|
||||
"周一",
|
||||
"周二",
|
||||
"周三",
|
||||
"周四",
|
||||
"周五",
|
||||
"周六",
|
||||
"周日"
|
||||
],
|
||||
LanguageEnum.EN: [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
]
|
||||
}
|
||||
|
||||
MONTH_DICT = {
|
||||
LanguageEnum.CN: [
|
||||
"1月",
|
||||
"2月",
|
||||
"3月",
|
||||
"4月",
|
||||
"5月",
|
||||
"6月",
|
||||
"7月",
|
||||
"8月",
|
||||
"9月",
|
||||
"10月",
|
||||
"11月",
|
||||
"12月",
|
||||
],
|
||||
LanguageEnum.EN: [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
]
|
||||
}
|
||||
|
||||
# Constants for the word 'none' in different languages
|
||||
NONE_WORD = {
|
||||
LanguageEnum.CN: "无",
|
||||
LanguageEnum.EN: "none"
|
||||
}
|
||||
|
||||
# Constants for the word 'repeated' in different languages
|
||||
REPEATED_WORD = {
|
||||
LanguageEnum.CN: "重复",
|
||||
LanguageEnum.EN: "repeated"
|
||||
}
|
||||
|
||||
# Constants for the word 'contradictory' in different languages
|
||||
CONTRADICTORY_WORD = {
|
||||
LanguageEnum.CN: "矛盾",
|
||||
LanguageEnum.EN: "contradiction"
|
||||
}
|
||||
|
||||
# Constants for the phrase 'included' in different languages
|
||||
CONTAINED_WORD = {
|
||||
LanguageEnum.CN: "被包含",
|
||||
LanguageEnum.EN: "contained"
|
||||
}
|
||||
|
||||
# Constants for the symbol ':' in different languages' representations
|
||||
COLON_WORD = {
|
||||
LanguageEnum.CN: ":",
|
||||
LanguageEnum.EN: ":"
|
||||
}
|
||||
|
||||
# Constants for the symbol ',' in different languages' representations
|
||||
COMMA_WORD = {
|
||||
LanguageEnum.CN: ",",
|
||||
LanguageEnum.EN: ","
|
||||
}
|
||||
|
||||
# Default human name placeholders for different languages
|
||||
DEFAULT_HUMAN_NAME = {
|
||||
LanguageEnum.CN: "用户",
|
||||
LanguageEnum.EN: "user"
|
||||
}
|
||||
|
||||
# Mapping of datetime terms from natural language to standardized keys for each supported language
|
||||
DATATIME_KEY_MAP = {
|
||||
LanguageEnum.CN: {
|
||||
"年": "year",
|
||||
"月": "month",
|
||||
"日": "day",
|
||||
"周": "week",
|
||||
"星期几": "weekday",
|
||||
},
|
||||
LanguageEnum.EN: {
|
||||
"Year": "year",
|
||||
"Month": "month",
|
||||
"Day": "day",
|
||||
"Week": "week",
|
||||
"Weekday": "weekday",
|
||||
}
|
||||
}
|
||||
|
||||
# Phrase for indicating inferred time in different languages
|
||||
TIME_INFER_WORD = {
|
||||
LanguageEnum.CN: "推断时间",
|
||||
LanguageEnum.EN: "Inference time"
|
||||
}
|
||||
|
||||
USER_NAME_EXPRESSION = {
|
||||
LanguageEnum.CN: "用户姓名是{name}。",
|
||||
LanguageEnum.EN: "User's name is {name}."
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
import datetime
|
||||
|
||||
from memoryscope.constants.common_constants import QUERY_WITH_TS
|
||||
from memoryscope.constants.language_constants import NONE_WORD
|
||||
from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
|
||||
from memoryscope.enumeration.message_role_enum import MessageRoleEnum
|
||||
|
||||
|
||||
class ExampleQueryWorker(MemoryBaseWorker):
|
||||
# NOTE: If you want to utilize the capabilities of the prompt handler, please be sure to include this sentence.
|
||||
file_path: str = __file__
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.rewrite_history_count: int = kwargs.get("rewrite_history_count", 2)
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
|
||||
def rewrite_query(self, query: str) -> str:
|
||||
chat_messages = self.chat_messages_scatter
|
||||
if len(chat_messages) <= 1:
|
||||
return query
|
||||
|
||||
if chat_messages[-1].role == MessageRoleEnum.USER:
|
||||
chat_messages = chat_messages[:-1]
|
||||
chat_messages = chat_messages[-self.rewrite_history_count:]
|
||||
|
||||
# get context
|
||||
context_list = []
|
||||
for message in chat_messages:
|
||||
context = message.content
|
||||
if len(context) > 200:
|
||||
context = context[:100] + context[-100:]
|
||||
if message.role == MessageRoleEnum.USER:
|
||||
context_list.append(f"{self.target_name}: {context}")
|
||||
elif message.role == MessageRoleEnum.ASSISTANT:
|
||||
context_list.append(f"Assistant: {context}")
|
||||
|
||||
if not context_list:
|
||||
return query
|
||||
|
||||
system_prompt = self.prompt_handler.rewrite_query_system
|
||||
user_query = self.prompt_handler.rewrite_query_query.format(query=query,
|
||||
context="\n".join(context_list))
|
||||
rewrite_query_message = self.prompt_to_msg(system_prompt=system_prompt,
|
||||
few_shot="",
|
||||
user_query=user_query)
|
||||
self.logger.info(f"rewrite_query_message={rewrite_query_message}")
|
||||
|
||||
# Invoke the LLM to generate a response
|
||||
response = self.generation_model.call(messages=rewrite_query_message,
|
||||
**self.generation_model_kwargs)
|
||||
|
||||
# Handle empty or unsuccessful responses
|
||||
if not response.status or not response.message.content:
|
||||
return query
|
||||
|
||||
response_text = response.message.content
|
||||
self.logger.info(f"rewrite_query.response_text={response_text}")
|
||||
|
||||
if not response_text or response_text.lower() == self.get_language_value(NONE_WORD):
|
||||
return query
|
||||
|
||||
return response_text
|
||||
|
||||
def _run(self):
|
||||
query = "" # Default query value
|
||||
timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
|
||||
|
||||
if "query" in self.chat_kwargs:
|
||||
# set query if exists
|
||||
query = self.chat_kwargs["query"]
|
||||
if not query:
|
||||
query = ""
|
||||
query = query.strip()
|
||||
|
||||
# set ts if exists
|
||||
_timestamp = self.chat_kwargs.get("timestamp")
|
||||
if _timestamp and isinstance(_timestamp, int):
|
||||
timestamp = _timestamp
|
||||
|
||||
if self.rewrite_history_count > 0:
|
||||
t_query = self.rewrite_query(query=query)
|
||||
if t_query:
|
||||
query = t_query
|
||||
|
||||
# Store the determined query and its timestamp in the context
|
||||
self.set_workflow_context(QUERY_WITH_TS, (query, timestamp))
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
rewrite_query_system:
|
||||
cn: |
|
||||
任务: 消除指代问题并重写
|
||||
要求: 检查提供的问题是否存在指代。如果存在指代,通过上下文信息重写问题,使其信息充足,能够单独回答。如果没有指代问题,则回答“无”。
|
||||
en: |
|
||||
Task: Eliminate referencing issues and rewrite
|
||||
Requirements: Check the provided questions for any references. If references exist, rewrite the questions using contextual information to make them sufficiently informative so they can be answered independently. If there are no referencing issues, respond with "None".
|
||||
|
||||
rewrite_query_query:
|
||||
cn: |
|
||||
上下文:
|
||||
{context}
|
||||
问题:{query}
|
||||
重写:
|
||||
|
||||
en: |
|
||||
Context:
|
||||
{context}
|
||||
Question: {query}
|
||||
Rewrite:
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
from .memoryscope import MemoryScope
|
||||
from .memoryscope_context import MemoryscopeContext
|
||||
|
||||
__all__ = [
|
||||
"MemoryScope",
|
||||
"MemoryscopeContext"
|
||||
]
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from .api_memory_chat import ApiMemoryChat
|
||||
from .base_memory_chat import BaseMemoryChat
|
||||
from .cli_memory_chat import CliMemoryChat
|
||||
|
||||
__all__ = [
|
||||
"ApiMemoryChat",
|
||||
"BaseMemoryChat",
|
||||
"CliMemoryChat"
|
||||
]
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
from typing import List, Optional, Literal
|
||||
|
||||
from memoryscope.constants.common_constants import MEMORIES
|
||||
from memoryscope.constants.language_constants import DEFAULT_HUMAN_NAME, USER_NAME_EXPRESSION
|
||||
from memoryscope.core.chat.base_memory_chat import BaseMemoryChat
|
||||
from memoryscope.core.memoryscope_context import MemoryscopeContext
|
||||
from memoryscope.core.models.base_model import BaseModel
|
||||
from memoryscope.core.service.base_memory_service import BaseMemoryService
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.utils.prompt_handler import PromptHandler
|
||||
from memoryscope.enumeration.message_role_enum import MessageRoleEnum
|
||||
from memoryscope.scheme.message import Message
|
||||
from memoryscope.scheme.model_response import ModelResponse, ModelResponseGen
|
||||
|
||||
|
||||
class ApiMemoryChat(BaseMemoryChat):
|
||||
|
||||
def __init__(self,
|
||||
memory_service: str,
|
||||
generation_model: str,
|
||||
context: MemoryscopeContext,
|
||||
stream: bool = False,
|
||||
**kwargs):
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._memory_service: BaseMemoryService | str = memory_service
|
||||
self._generation_model: BaseModel | str = generation_model
|
||||
self.context: MemoryscopeContext = context
|
||||
self.stream: bool = stream
|
||||
self.generation_model_kwargs: dict = kwargs.pop("generation_model_kwargs", {})
|
||||
|
||||
self._prompt_handler: PromptHandler | None = None
|
||||
|
||||
@property
|
||||
def prompt_handler(self) -> PromptHandler:
|
||||
"""
|
||||
Lazy initialization property for the prompt handler.
|
||||
|
||||
This property ensures that the `_prompt_handler` attribute is only instantiated when it is first accessed.
|
||||
It uses the current file's path and additional keyword arguments for configuration.
|
||||
|
||||
Returns:
|
||||
PromptHandler: An instance of the PromptHandler configured for this CLI session.
|
||||
"""
|
||||
if self._prompt_handler is None:
|
||||
self._prompt_handler = PromptHandler(__file__,
|
||||
language=self.context.language,
|
||||
prompt_file="memory_chat_prompt",
|
||||
**self.kwargs)
|
||||
return self._prompt_handler
|
||||
|
||||
@property
|
||||
def memory_service(self) -> BaseMemoryService:
|
||||
"""
|
||||
Property to access the memory service. If the service is initially set as a string,
|
||||
it will be looked up in the memory service dictionary of context, initialized,
|
||||
and then returned as an instance of `BaseMemoryService`. Ensures the memory service
|
||||
is properly started before use.
|
||||
|
||||
Returns:
|
||||
BaseMemoryService: An active memory service instance.
|
||||
|
||||
Raises:
|
||||
ValueError: If the declaration of memory service is not found in the memory service dictionary of context.
|
||||
"""
|
||||
if isinstance(self._memory_service, str):
|
||||
if self._memory_service not in self.context.memory_service_dict:
|
||||
raise ValueError(f"Missing declaration of memory_service in context: {self._memory_service}")
|
||||
|
||||
self._memory_service: BaseMemoryService = self.context.memory_service_dict[self._memory_service]
|
||||
# init service & update kwargs
|
||||
self._memory_service.init_service()
|
||||
return self._memory_service
|
||||
|
||||
@property
|
||||
def human_name(self):
|
||||
return self.memory_service.human_name
|
||||
|
||||
@property
|
||||
def assistant_name(self):
|
||||
return self.memory_service.assistant_name
|
||||
|
||||
@property
|
||||
def generation_model(self) -> BaseModel:
|
||||
"""
|
||||
Property to get the generation model. If the model is set as a string, it will be resolved from the global
|
||||
context's model dictionary.
|
||||
|
||||
Raises:
|
||||
ValueError: If the declaration of generation model is not found in the model dictionary of context .
|
||||
|
||||
Returns:
|
||||
BaseModel: An actual generation model instance.
|
||||
"""
|
||||
if isinstance(self._generation_model, str):
|
||||
if self._generation_model not in self.context.model_dict:
|
||||
raise ValueError(f"Missing declaration of generation model in yaml config: {self._generation_model}")
|
||||
self._generation_model = self.context.model_dict[self._generation_model]
|
||||
return self._generation_model
|
||||
|
||||
def iter_response(self,
|
||||
remember_response: bool,
|
||||
resp: ModelResponseGen,
|
||||
memories: str,
|
||||
query_message: Message) -> ModelResponseGen:
|
||||
|
||||
model_response: ModelResponse | None = None
|
||||
for model_response in resp:
|
||||
yield model_response
|
||||
|
||||
if remember_response:
|
||||
if model_response and model_response.message:
|
||||
model_response.message.role_name = self.assistant_name
|
||||
model_response.meta_data[MEMORIES] = memories
|
||||
self.memory_service.add_messages_pair([query_message, model_response.message])
|
||||
else:
|
||||
self.logger.warning("model_response or model_response.message is empty!")
|
||||
|
||||
def chat_with_memory(self,
|
||||
query: str,
|
||||
role_name: Optional[str] = None,
|
||||
system_prompt: Optional[str] = None,
|
||||
memory_prompt: Optional[str] = None,
|
||||
temporary_memories: Optional[str] = None,
|
||||
history_message_strategy: Literal["auto", None] | int = "auto",
|
||||
remember_response: bool = True,
|
||||
**kwargs):
|
||||
|
||||
chat_messages: List[Message] = []
|
||||
|
||||
# prepare query message
|
||||
if not role_name:
|
||||
role_name = self.human_name
|
||||
query_message = Message(role=MessageRoleEnum.USER.value, role_name=role_name, content=query)
|
||||
|
||||
# To retrieve memory, prepare the query timestamp and role name by adding query_message.
|
||||
memories: str = self.memory_service.retrieve_memory(query=query_message.content,
|
||||
role_name=query_message.role_name,
|
||||
timestamp=query_message.time_created)
|
||||
|
||||
# format system_message with memories
|
||||
system_prompt_list = []
|
||||
if system_prompt:
|
||||
system_prompt_list.append(system_prompt)
|
||||
else:
|
||||
dt_handler = DatetimeHandler()
|
||||
date_time = dt_handler.datetime_format("%Y-%m-%d %H:%M:%S")
|
||||
weekday = dt_handler.get_dt_info_dict(self.context.language)["weekday"]
|
||||
system_prompt_list.append(self.prompt_handler.system_prompt.format(date_time=f"{date_time} {weekday}"))
|
||||
|
||||
if memories:
|
||||
# add memory prompt
|
||||
if memory_prompt:
|
||||
system_prompt_list.append(memory_prompt)
|
||||
else:
|
||||
system_prompt_list.append(self.prompt_handler.memory_prompt)
|
||||
|
||||
if self.human_name != DEFAULT_HUMAN_NAME[self.context.language]:
|
||||
system_prompt_list.append(USER_NAME_EXPRESSION[self.context.language].format(name=self.human_name))
|
||||
system_prompt_list.append(memories)
|
||||
|
||||
if temporary_memories:
|
||||
system_prompt_list.extend(temporary_memories)
|
||||
|
||||
system_prompt_join = "\n".join([x.strip() for x in system_prompt_list])
|
||||
system_message = Message(role=MessageRoleEnum.SYSTEM.value, content=system_prompt_join)
|
||||
chat_messages.append(system_message)
|
||||
|
||||
# Include past conversation history in the message list
|
||||
if history_message_strategy:
|
||||
history_messages = []
|
||||
|
||||
if history_message_strategy == "auto":
|
||||
history_messages = self.memory_service.read_message()
|
||||
|
||||
elif isinstance(history_message_strategy, int):
|
||||
history_messages = self.memory_service.get_chat_messages_scatter(history_message_strategy)
|
||||
|
||||
if history_messages:
|
||||
assert isinstance(history_messages[0], Message)
|
||||
chat_messages.extend(history_messages)
|
||||
|
||||
# Append the current user's message to the conversation context
|
||||
chat_messages.append(query_message)
|
||||
self.logger.info(f"chat_messages={chat_messages}")
|
||||
|
||||
resp = self.generation_model.call(messages=chat_messages, stream=self.stream, **self.generation_model_kwargs)
|
||||
if self.stream:
|
||||
return self.iter_response(remember_response, resp, memories, query_message)
|
||||
|
||||
else:
|
||||
model_response: ModelResponse = resp
|
||||
if remember_response:
|
||||
if model_response and model_response.message:
|
||||
model_response.message.role_name = self.assistant_name
|
||||
model_response.meta_data[MEMORIES] = memories
|
||||
self.memory_service.add_messages_pair([query_message, model_response.message])
|
||||
else:
|
||||
self.logger.warning("model_response or model_response.message is empty!")
|
||||
return model_response
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
from typing import Optional, Literal
|
||||
|
||||
from memoryscope.core.service.base_memory_service import BaseMemoryService
|
||||
from memoryscope.core.utils.logger import Logger
|
||||
|
||||
|
||||
class BaseMemoryChat(metaclass=ABCMeta):
|
||||
"""
|
||||
An abstract base class representing a chat system integrated with memory services.
|
||||
It outlines the method to initiate a chat session leveraging memory data, which concrete subclasses must implement.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs: dict = kwargs
|
||||
self.logger = Logger.get_logger()
|
||||
|
||||
@property
|
||||
def memory_service(self) -> BaseMemoryService:
|
||||
"""
|
||||
Abstract property to access the memory service.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: This method should be implemented in a subclass.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def chat_with_memory(self,
|
||||
query: str,
|
||||
role_name: Optional[str] = None,
|
||||
system_prompt: Optional[str] = None,
|
||||
memory_prompt: Optional[str] = None,
|
||||
temporary_memories: Optional[str] = None,
|
||||
history_message_strategy: Literal["auto", None] | int = "auto",
|
||||
remember_response: bool = True,
|
||||
**kwargs):
|
||||
"""
|
||||
The core function that carries out conversation with memory accepts user queries through query and returns the
|
||||
conversation results through model_response. The retrieved memories are stored in the memories within meta_data.
|
||||
Args:
|
||||
query (str): User's query, includes the user's question.
|
||||
role_name (str, optional): User's role name.
|
||||
system_prompt (str, optional): System prompt. Defaults to the system_prompt in "memory_chat_prompt.yaml".
|
||||
memory_prompt (str, optional): Memory prompt, It takes effect when there is a memory and will be placed in
|
||||
front of the retrieved memory. Defaults to the memory_prompt in "memory_chat_prompt.yaml".
|
||||
temporary_memories (str, optional): Manually added user memory in this function.
|
||||
history_message_strategy ("auto", None, int):
|
||||
- If it is set to "auto", the history messages in the conversation will retain those that have not
|
||||
yet been summarized. Default to "auto".
|
||||
- If it is set to None, no conversation history will be saved.
|
||||
- If it is set to an integer value "n", recent "n" message-pair[user, assistant] will be retained.
|
||||
remember_response (bool, optional): Flag indicating whether to save the AI's response to memory.
|
||||
Defaults to False.
|
||||
Returns:
|
||||
- ModelResponse: In non-streaming mode, returns a complete AI response.
|
||||
- ModelResponseGen: In streaming mode, returns a generator yielding AI response parts.
|
||||
- Memories: To obtain the memory by invoking the method of model_response.meta_data[MEMORIES]
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def start_backend_service(self, **kwargs):
|
||||
self.memory_service.start_backend_service(**kwargs)
|
||||
|
||||
def run_service_operation(self, name: str, role_name: Optional[str] = None, **kwargs):
|
||||
return self.memory_service.run_operation(name, role_name=role_name, **kwargs)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Abstract method to run the chat system.
|
||||
|
||||
This method should contain the logic to initiate and manage the chat process,
|
||||
utilizing the memory service as needed. It must be implemented by subclasses.
|
||||
"""
|
||||
pass
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
from typing import Optional, Literal
|
||||
|
||||
import questionary
|
||||
|
||||
from memoryscope.core.chat.api_memory_chat import ApiMemoryChat
|
||||
from memoryscope.core.utils.tool_functions import char_logo
|
||||
|
||||
|
||||
class CliMemoryChat(ApiMemoryChat):
|
||||
"""
|
||||
Command-line interface for chatting with an AI that integrates memory functionality.
|
||||
Allows users to interact, manage chat history, adjust streaming settings, and view commands' help.
|
||||
"""
|
||||
USER_COMMANDS = {
|
||||
"exit": "Exit the CLI.",
|
||||
"clear": "Clear the command history.",
|
||||
"help": "Display available CLI commands and their descriptions.",
|
||||
"stream": "Toggle between getting streamed responses from the model."
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._logo = char_logo("MemoryScope")
|
||||
|
||||
def print_logo(self):
|
||||
"""
|
||||
Prints the logo of the CLI application to the console.
|
||||
|
||||
The logo is composed of multiple lines, which are iterated through
|
||||
and printed one by one to provide a visual identity for the chat interface.
|
||||
"""
|
||||
for line in self._logo:
|
||||
print(line)
|
||||
|
||||
def chat_with_memory(self,
|
||||
query: str,
|
||||
role_name: Optional[str] = None,
|
||||
system_prompt: Optional[str] = None,
|
||||
memory_prompt: Optional[str] = None,
|
||||
temporary_memories: Optional[str] = None,
|
||||
history_message_strategy: Literal["auto", None] | int = "auto",
|
||||
remember_response: bool = True,
|
||||
**kwargs):
|
||||
resp = super().chat_with_memory(query=query,
|
||||
role_name=role_name,
|
||||
system_prompt=system_prompt,
|
||||
memory_prompt=memory_prompt,
|
||||
temporary_memories=temporary_memories,
|
||||
history_message_strategy=history_message_strategy,
|
||||
remember_response=remember_response,
|
||||
**kwargs)
|
||||
|
||||
if self.stream:
|
||||
for _resp in resp:
|
||||
questionary.print(_resp.delta, end="")
|
||||
questionary.print("")
|
||||
else:
|
||||
questionary.print(resp.message.content)
|
||||
|
||||
@staticmethod
|
||||
def parse_query_command(query: str):
|
||||
"""
|
||||
Parses the user's input query command, separating it into the command and its associated keyword arguments.
|
||||
|
||||
Args:
|
||||
query (str): The raw input string from the user which includes the command and its arguments.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the command (str) as the first element and a dictionary (kwargs) of keyword
|
||||
arguments as the second element.
|
||||
"""
|
||||
query_split = query.lstrip("/").lower().split(" ") # Split and preprocess the input command
|
||||
command = query_split[0] # Extract the command
|
||||
args = query_split[1:] # Extract the arguments following the command
|
||||
kwargs = {} # Initialize dictionary to hold keyword arguments
|
||||
|
||||
for arg in args:
|
||||
# Skip if no arguments exist (unnecessary check due to prior assignment, but retained as per original)
|
||||
if not args:
|
||||
continue
|
||||
arg_split = arg.split("=") # Split argument into key-value pair
|
||||
if len(arg_split) >= 2: # Ensure there's both a key and value
|
||||
k = arg_split[0] # Extract key
|
||||
v = arg_split[1] # Extract value
|
||||
if k and v: # Only add to kwargs if both key and value are non-empty
|
||||
kwargs[k] = v
|
||||
|
||||
return command, kwargs # Return the parsed command and keyword arguments
|
||||
|
||||
def process_commands(self, query: str) -> bool:
|
||||
"""
|
||||
Parses and executes commands from user input in the CLI chat interface.
|
||||
Supports operations like exiting, clearing screen, showing help, toggling stream mode,
|
||||
executing predefined memory operations, and handling unknown commands.
|
||||
|
||||
Args:
|
||||
query (str): The user's input command string.
|
||||
|
||||
Returns:
|
||||
bool: Indicates whether to continue running the CLI after processing the command.
|
||||
"""
|
||||
continue_run = True
|
||||
command, kwargs = self.parse_query_command(query)
|
||||
|
||||
# Print prompt for AI's response
|
||||
questionary.print("> ", end="", style="fg:yellow")
|
||||
questionary.print(f"{self.assistant_name}: ", end="", style="bold")
|
||||
|
||||
if command == "exit":
|
||||
self.memory_service.stop_backend_service()
|
||||
continue_run = False
|
||||
|
||||
elif command == "clear":
|
||||
os.system("clear")
|
||||
|
||||
elif command == "help":
|
||||
questionary.print("CLI commands", "bold")
|
||||
for cmd, desc in self.USER_COMMANDS.items():
|
||||
questionary.print(text=f" /{cmd}:", style="bold")
|
||||
questionary.print(text=f" {desc}")
|
||||
|
||||
elif command == "stream":
|
||||
self.stream = not self.stream
|
||||
questionary.print(f"set stream: {self.stream}")
|
||||
|
||||
elif command in self.memory_service.op_description_dict:
|
||||
refresh_time = kwargs.pop("refresh_time", "")
|
||||
if refresh_time and refresh_time.isdigit():
|
||||
refresh_time = int(refresh_time)
|
||||
self.memory_service.stop_backend_service()
|
||||
while True:
|
||||
result = self.memory_service.run_operation(name=command, **kwargs)
|
||||
if os.name == 'nt':
|
||||
os.system('cls')
|
||||
else:
|
||||
os.system('clear')
|
||||
self.print_logo()
|
||||
if result:
|
||||
if isinstance(result, list):
|
||||
result = "\n".join([str(x) for x in result])
|
||||
questionary.print(result)
|
||||
else:
|
||||
questionary.print(f"command={command} result is empty! kwargs={kwargs}")
|
||||
time.sleep(refresh_time)
|
||||
|
||||
else:
|
||||
result = self.memory_service.run_operation(name=command, **kwargs)
|
||||
if result:
|
||||
if isinstance(result, list):
|
||||
result = "\n".join([str(x) for x in result])
|
||||
questionary.print(result)
|
||||
else:
|
||||
questionary.print(f"command={command} result is empty! kwargs={kwargs}")
|
||||
|
||||
else:
|
||||
questionary.print(f"Unknown command={command} received.")
|
||||
|
||||
return continue_run
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the CLI chat loop, which handles user input, processes commands,
|
||||
communicates with the AI model, manages conversation memory, and controls
|
||||
the chat session including streaming responses, command execution, and error handling.
|
||||
|
||||
The loop continues until the user explicitly chooses to exit.
|
||||
"""
|
||||
self.print_logo()
|
||||
self.USER_COMMANDS.update(self.memory_service.op_description_dict)
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = questionary.text(message=f"{self.human_name}:", multiline=False, qmark=">").unsafe_ask()
|
||||
if not query:
|
||||
continue
|
||||
|
||||
query: str = query.strip()
|
||||
|
||||
# Handle special commands prefixed with '/'
|
||||
if query.startswith("/"):
|
||||
if self.process_commands(query=query):
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
# Print prompt for AI's response
|
||||
questionary.print("> ", end="", style="fg:yellow")
|
||||
questionary.print(f"{self.assistant_name}: ", end="", style="bold")
|
||||
|
||||
# Fetch and display AI's response
|
||||
self.start_backend_service()
|
||||
self.chat_with_memory(query=query)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# Handle user interruption and confirm exit
|
||||
questionary.print("User interrupt occurred.")
|
||||
is_exit = questionary.confirm("Continue exit?").unsafe_ask()
|
||||
if is_exit:
|
||||
self.memory_service.stop_backend_service()
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
# Log and handle any unanticipated exceptions
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.exception(f"An exception occurred when running cli memory chat. args={e.args}.")
|
||||
continue
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
system_prompt:
|
||||
cn: |
|
||||
你是一个名为MemoryScope的智能助理,请用中文简洁地回答用户问题。当前时间是{date_time}。
|
||||
en: |
|
||||
You are a helpful assistant named MemoryScope, please answer questions concisely in English. The current time is {date_time}.
|
||||
|
||||
memory_prompt:
|
||||
cn: |
|
||||
在回答用户问题时,请尽量忘记大部分不相关的信息。只有当信息与用户问题或对话内容非常相关时,才记住这些信息并加以使用。请确保你的回答简洁、准确,并聚焦于用户问题或对话主题。信息:
|
||||
en: |
|
||||
When responding to user questions, please try to forget most of the irrelevant information. Only remember and use the information if it is highly relevant to the current question or conversation. Ensure that your answers are concise, accurate, and focused on the user's current question or the topic of discussion. Information:
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
from .arguments import Arguments
|
||||
from .config_manager import ConfigManager
|
||||
|
||||
__all__ = [
|
||||
"Arguments",
|
||||
"ConfigManager",
|
||||
]
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Literal, Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class Arguments(object):
|
||||
language: Literal["cn", "en"] = field(default="cn", metadata={"help": "support en & cn now"})
|
||||
|
||||
thread_pool_max_workers: int = field(default=5, metadata={"help": "thread pool max workers"})
|
||||
|
||||
memory_chat_class: str = field(default="cli_memory_chat", metadata={
|
||||
"help": "cli_memory_chat(Command-line interaction), api_memory_chat(API interface interaction), etc."})
|
||||
|
||||
chat_stream: bool | None = field(default=None, metadata={
|
||||
"help": "In the case of cli_memory_chat, stream mode is recommended. For api_memory_chat mode, "
|
||||
"please use non-stream. If set to None, the value will be automatically determined."})
|
||||
|
||||
human_name: str = field(default="user", metadata={"help": "Human user's name"})
|
||||
|
||||
assistant_name: str = field(default="AI", metadata={"help": "assistant' name"})
|
||||
|
||||
consolidate_memory_interval_time: int | None = field(default=1, metadata={
|
||||
"help": "Memory backend service: If you feel that the token consumption is relatively high, "
|
||||
"please increase the time interval. When set to None, the value will not be updated."})
|
||||
|
||||
reflect_and_reconsolidate_interval_time: int | None = field(default=15, metadata={
|
||||
"help": "Memory backend service: If you feel that the token consumption is relatively high, "
|
||||
"please increase the time interval. When set to None, the value will not be updated."})
|
||||
|
||||
worker_params: Dict[str, dict] = field(default_factory=lambda: {}, metadata={
|
||||
"help": "dict format: worker_name -> param_key -> param_value"})
|
||||
|
||||
generation_backend: str = field(default="dashscope_generation", metadata={
|
||||
"help": "global generation backend: openai_generation, dashscope_generation, etc."})
|
||||
|
||||
generation_model: str = field(default="qwen-max", metadata={
|
||||
"help": "global generation model: gpt-4o, gpt-4o-mini, gpt-4-turbo, qwen-max, etc."})
|
||||
|
||||
generation_params: dict = field(default_factory=lambda: {}, metadata={
|
||||
"help": "global generation params: max_tokens, top_p, temperature, etc."})
|
||||
|
||||
embedding_backend: str = field(default="dashscope_generation", metadata={
|
||||
"help": "global embedding backend: openai_embedding, dashscope_embedding, etc."})
|
||||
|
||||
embedding_model: str = field(default="text-embedding-v2", metadata={
|
||||
"help": "global embedding model: text-embedding-3-large, text-embedding-3-small, text-embedding-ada-002, "
|
||||
"text-embedding-v2, etc."})
|
||||
|
||||
embedding_params: dict = field(default_factory=lambda: {})
|
||||
|
||||
rank_backend: str = field(default="dashscope_rank", metadata={"help": "global rank backend: dashscope_rank, etc."})
|
||||
|
||||
rank_model: str = field(default="gte-rerank", metadata={"help": "global rank model: gte-rerank, etc."})
|
||||
|
||||
rank_params: dict = field(default_factory=lambda: {})
|
||||
|
||||
es_index_name: str = field(default="memory_index")
|
||||
|
||||
es_url: str = field(default="http://localhost:9200")
|
||||
|
||||
retrieve_mode: str = field(default="dense", metadata={
|
||||
"help": "retrieve_mode: dense, sparse(not implemented), hybrid(not implemented)"})
|
||||
|
||||
enable_ranker: bool = field(default=False, metadata={
|
||||
"help": "If a semantic ranking model is not available, MemoryScope will use cosine similarity scoring as a "
|
||||
"substitute. However, the ranking effectiveness will be somewhat compromised.",
|
||||
"map_yaml": "global->enable_ranker"})
|
||||
|
||||
enable_today_contra_repeat: bool = field(default=True, metadata={
|
||||
"help": "Whether enable conflict resolution and deduplication for the day? "
|
||||
"Note that enabling this will increase token consumption.",
|
||||
"map_yaml": "global->enable_today_contra_repeat"})
|
||||
|
||||
enable_long_contra_repeat: bool = field(default=False, metadata={
|
||||
"help": "Whether to enable long-term conflict resolution and deduplication. "
|
||||
"Note that enabling this will increase token consumption.",
|
||||
"map_yaml": "global->enable_long_contra_repeat"})
|
||||
|
||||
output_memory_max_count: int = field(default=20, metadata={
|
||||
"help": "The maximum number of memories retrieved during memory recall.",
|
||||
"map_yaml": "global->output_memory_max_count"})
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from dataclasses import fields
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Literal
|
||||
|
||||
import yaml
|
||||
|
||||
from memoryscope.core.config.arguments import Arguments
|
||||
from memoryscope.core.utils.logger import Logger
|
||||
|
||||
|
||||
class ConfigManager(object):
|
||||
|
||||
def __init__(self,
|
||||
config_path: Optional[str] = None,
|
||||
arguments: Optional[Arguments] = None,
|
||||
demo_config_name: str = "demo_config_zh.yaml",
|
||||
**kwargs):
|
||||
self.config: dict = {}
|
||||
self.kwargs = kwargs
|
||||
self.logger = Logger.get_logger("memoryscope")
|
||||
|
||||
if not (config_path or kwargs or arguments):
|
||||
raise RuntimeError("can not init config manager without kwargs or --config_path!")
|
||||
|
||||
if config_path:
|
||||
self.read_config(config_path)
|
||||
else:
|
||||
self.read_config((Path(__file__).parent / demo_config_name).__str__())
|
||||
|
||||
kwargs = {k: v for k, v in kwargs.items() if k in [x.name for x in fields(Arguments)]}
|
||||
kwargs_padding = {x.name: None for x in fields(Arguments) if x.name not in kwargs}
|
||||
kwargs.update(kwargs_padding)
|
||||
|
||||
# (high) when there are environment variables, read them and merge into kwargs
|
||||
kwargs_from_env = {x.name:os.environ.get(x.name, None) for x in fields(Arguments) if os.environ.get(x.name, None) is not None}
|
||||
kwargs.update(kwargs_from_env)
|
||||
|
||||
# generate argument dataclass
|
||||
if not arguments:
|
||||
arguments = Arguments(**kwargs)
|
||||
else:
|
||||
# (highest) when arguments is passed into the memoryscope
|
||||
arguments = arguments
|
||||
|
||||
self.update_config_by_arguments(arguments)
|
||||
self.logger.info("\n" + self.dump_config())
|
||||
|
||||
def read_config(self, config_path: str):
|
||||
if config_path.endswith(".yaml"):
|
||||
with open(config_path) as f:
|
||||
self.config = yaml.load(f, yaml.FullLoader)
|
||||
|
||||
elif config_path.endswith(".json"):
|
||||
with open(config_path) as f:
|
||||
self.config = json.load(f)
|
||||
|
||||
@staticmethod
|
||||
def update_ignore_none(config, new_config_dict):
|
||||
update_dict = {k:v for k, v in new_config_dict.items() if v is not None}
|
||||
config.update(update_dict)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def update_global_by_arguments(config: dict, arguments: Arguments):
|
||||
ConfigManager.update_ignore_none(
|
||||
config,
|
||||
{
|
||||
"language": arguments.language,
|
||||
"thread_pool_max_workers": arguments.thread_pool_max_workers,
|
||||
"enable_ranker": arguments.enable_ranker,
|
||||
"enable_today_contra_repeat": arguments.enable_today_contra_repeat,
|
||||
"enable_long_contra_repeat": arguments.enable_long_contra_repeat,
|
||||
"output_memory_max_count": arguments.output_memory_max_count,
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_memory_chat_by_arguments(config: dict, arguments: Arguments):
|
||||
if arguments.memory_chat_class is not None:
|
||||
memory_chat_class_split = config["class"].split(".")
|
||||
stream = arguments.chat_stream
|
||||
if stream is None:
|
||||
stream = arguments.memory_chat_class in ["cli_memory_chat", ]
|
||||
config.update(
|
||||
{
|
||||
"class": ".".join(memory_chat_class_split[:-1] + [arguments.memory_chat_class]),
|
||||
"stream": stream,
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_memory_service_by_arguments(config: dict, arguments: Arguments):
|
||||
ConfigManager.update_ignore_none(config, {
|
||||
"human_name": arguments.human_name,
|
||||
"assistant_name": arguments.assistant_name,
|
||||
})
|
||||
if arguments.consolidate_memory_interval_time is not None:
|
||||
config["memory_operations"]["consolidate_memory"]["interval_time"] = \
|
||||
arguments.consolidate_memory_interval_time
|
||||
|
||||
if arguments.reflect_and_reconsolidate_interval_time is not None:
|
||||
config["memory_operations"]["reflect_and_reconsolidate"]["interval_time"] = \
|
||||
arguments.reflect_and_reconsolidate_interval_time
|
||||
|
||||
@staticmethod
|
||||
def update_worker_by_arguments(config: dict, arguments: Arguments):
|
||||
if arguments.worker_params is not None:
|
||||
for worker_name, kv_dict in arguments.worker_params.items():
|
||||
if worker_name not in config:
|
||||
continue
|
||||
config[worker_name].update(kv_dict)
|
||||
|
||||
@staticmethod
|
||||
def update_model_by_arguments(config: dict, arguments: Arguments):
|
||||
ConfigManager.update_ignore_none(config["generation_model"], {
|
||||
"module_name": arguments.generation_backend,
|
||||
"model_name": arguments.generation_model,
|
||||
})
|
||||
if isinstance(arguments.generation_params, dict):
|
||||
ConfigManager.update_ignore_none(config["generation_model"], {
|
||||
**arguments.generation_params,
|
||||
})
|
||||
|
||||
ConfigManager.update_ignore_none(config["embedding_model"], {
|
||||
"module_name": arguments.embedding_backend,
|
||||
"model_name": arguments.embedding_model,
|
||||
})
|
||||
if isinstance(arguments.embedding_params, dict):
|
||||
ConfigManager.update_ignore_none(config["embedding_model"], {
|
||||
**arguments.embedding_params,
|
||||
})
|
||||
|
||||
ConfigManager.update_ignore_none(config["rank_model"], {
|
||||
"module_name": arguments.rank_backend,
|
||||
"model_name": arguments.rank_model,
|
||||
})
|
||||
if isinstance(arguments.rank_params, dict):
|
||||
ConfigManager.update_ignore_none(config["rank_model"], {
|
||||
**arguments.rank_params,
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def update_memory_store_by_arguments(config: dict, arguments: Arguments):
|
||||
ConfigManager.update_ignore_none(config, {
|
||||
"index_name": arguments.es_index_name,
|
||||
"es_url": arguments.es_url,
|
||||
"retrieve_mode": arguments.retrieve_mode}
|
||||
)
|
||||
|
||||
def update_config_by_arguments(self, arguments: Arguments):
|
||||
# prepare global
|
||||
self.update_global_by_arguments(self.config["global"], arguments)
|
||||
|
||||
# prepare memory chat
|
||||
memory_chat_conf_dict = self.config["memory_chat"]
|
||||
memory_chat_config = list(memory_chat_conf_dict.values())[0]
|
||||
self.update_memory_chat_by_arguments(memory_chat_config, arguments)
|
||||
|
||||
# prepare memory service
|
||||
memory_service_conf_dict = self.config["memory_service"]
|
||||
memory_service_config = list(memory_service_conf_dict.values())[0]
|
||||
self.update_memory_service_by_arguments(memory_service_config, arguments)
|
||||
|
||||
# prepare worker
|
||||
self.update_worker_by_arguments(self.config["worker"], arguments)
|
||||
|
||||
# prepare model
|
||||
self.update_model_by_arguments(self.config["model"], arguments)
|
||||
|
||||
# prepare memory store
|
||||
self.update_memory_store_by_arguments(self.config["memory_store"], arguments)
|
||||
|
||||
def add_node_object(self, node: str, name: str, config: dict):
|
||||
self.config[node][name] = config
|
||||
|
||||
def pop_node_object(self, node: str, name: str):
|
||||
return self.config[node].pop(name, None)
|
||||
|
||||
def clear_node_all(self, node: str):
|
||||
self.config[node].clear()
|
||||
|
||||
def dump_config(self, file_type: Literal["json", "yaml"] = "yaml", file_path: Optional[str] = None) -> str:
|
||||
if file_type == "json":
|
||||
content = json.dumps(self.config, indent=2, ensure_ascii=False)
|
||||
elif file_type == "yaml":
|
||||
content = yaml.dump(self.config, indent=2, allow_unicode=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if file_path:
|
||||
with open(file_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return content
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
global:
|
||||
language: en
|
||||
thread_pool_max_workers: 5
|
||||
enable_ranker: false
|
||||
enable_today_contra_repeat: true
|
||||
enable_long_contra_repeat: false
|
||||
output_memory_max_count: 20
|
||||
|
||||
memory_chat:
|
||||
cli_memory_chat:
|
||||
class: core.chat.cli_memory_chat
|
||||
memory_service: memoryscope_service
|
||||
generation_model: generation_model
|
||||
stream: true
|
||||
|
||||
memory_service:
|
||||
memoryscope_service:
|
||||
class: core.service.memory_scope_service
|
||||
human_name: user
|
||||
assistant_name: AI
|
||||
memory_operations:
|
||||
read_message:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: read_message
|
||||
description: "read short memory"
|
||||
|
||||
retrieve_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,[extract_time|retrieve_obs_ins,semantic_rank],fuse_rerank
|
||||
description: "retrieve long-term memory"
|
||||
|
||||
list_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_top_memory,print_memory
|
||||
description: "read all long-term memory of the user, use `refresh_time=5` to refresh screen every 5 seconds."
|
||||
|
||||
delete_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_memory
|
||||
description: "delete a single long-term memory"
|
||||
|
||||
delete_all:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_all
|
||||
description: "delete all long-term memory"
|
||||
|
||||
add_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: add_memory
|
||||
description: "add a single observation"
|
||||
|
||||
consolidate_memory:
|
||||
class: core.operation.consolidate_memory_op
|
||||
workflow: info_filter,[get_observation|get_observation_with_time|load_today_memory],contra_repeat,store_memory
|
||||
description: "summary user's observation memory, run backend."
|
||||
interval_time: 1
|
||||
|
||||
reflect_and_reconsolidate:
|
||||
class: core.operation.backend_operation
|
||||
workflow: load_obs_and_insight,get_reflection_subject,update_insight,long_contra_repeat,store_memory
|
||||
description: "summary user's insight memory, run backend."
|
||||
interval_time: 15
|
||||
|
||||
worker:
|
||||
dummy:
|
||||
class: core.worker.dummy_worker
|
||||
generation_model: generation_model
|
||||
embedding_model: embedding_model
|
||||
rank_model: rank_model
|
||||
read_message:
|
||||
class: core.worker.frontend.read_message_worker
|
||||
set_query:
|
||||
class: core.worker.frontend.set_query_worker
|
||||
retrieve_obs_ins:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
extract_time:
|
||||
class: core.worker.frontend.extract_time_worker
|
||||
generation_model: generation_model
|
||||
semantic_rank:
|
||||
class: core.worker.frontend.semantic_rank_worker
|
||||
rank_model: rank_model
|
||||
fuse_rerank:
|
||||
class: core.worker.frontend.fuse_rerank_worker
|
||||
fuse_score_threshold: 0.01
|
||||
fuse_ratio_dict:
|
||||
conversation: 0.5
|
||||
observation: 1
|
||||
obs_customized: 1.2
|
||||
insight: 2.0
|
||||
fuse_time_ratio: 2.0
|
||||
retrieve_top_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
retrieve_expired_top_k: 100
|
||||
print_memory:
|
||||
class: core.worker.frontend.print_memory_worker
|
||||
retrieve_all_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 1000
|
||||
retrieve_ins_top_k: 1000
|
||||
retrieve_expired_top_k: 1000
|
||||
delete_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_memory
|
||||
delete_all:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_all
|
||||
add_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_query
|
||||
info_filter:
|
||||
class: core.worker.backend.info_filter_worker
|
||||
generation_model: generation_model
|
||||
load_today_memory:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_today_top_k: 100
|
||||
get_observation:
|
||||
class: core.worker.backend.get_observation_worker
|
||||
generation_model: generation_model
|
||||
get_observation_with_time:
|
||||
class: core.worker.backend.get_observation_with_time_worker
|
||||
generation_model: generation_model
|
||||
contra_repeat:
|
||||
class: core.worker.backend.contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
store_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_memory_key
|
||||
memory_key: all
|
||||
load_obs_and_insight:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_not_reflected_top_k: 100
|
||||
retrieve_not_updated_top_k: 100
|
||||
retrieve_insight_top_k: 100
|
||||
get_reflection_subject:
|
||||
class: core.worker.backend.get_reflection_subject_worker
|
||||
generation_model: generation_model
|
||||
reflect_obs_cnt_threshold: 6
|
||||
update_insight:
|
||||
class: core.worker.backend.update_insight_worker
|
||||
generation_model: generation_model
|
||||
rank_model: rank_model
|
||||
embedding_model: embedding_model
|
||||
update_insight_threshold: 0.01
|
||||
enable_parallel: false
|
||||
long_contra_repeat:
|
||||
class: core.worker.backend.long_contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
long_contra_repeat_threshold: 0.5
|
||||
|
||||
model:
|
||||
generation_model:
|
||||
class: core.models.llama_index_generation_model
|
||||
module_name: openai_generation
|
||||
model_name: gpt-4o
|
||||
max_tokens: 2000
|
||||
temperature: 0.01
|
||||
embedding_model:
|
||||
class: core.models.llama_index_embedding_model
|
||||
module_name: openai_embedding
|
||||
model_name: text-embedding-3-small
|
||||
rank_model:
|
||||
class: core.models.llama_index_rank_model
|
||||
module_name: dashscope_rank
|
||||
model_name: gte-rerank
|
||||
top_n: 500
|
||||
|
||||
memory_store:
|
||||
class: core.storage.llama_index_es_memory_store
|
||||
embedding_model: embedding_model
|
||||
index_name: memory_index
|
||||
es_url: http://localhost:9200
|
||||
retrieve_mode: dense
|
||||
|
||||
monitor:
|
||||
class: core.storage.dummy_monitor
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
global:
|
||||
language: cn
|
||||
thread_pool_max_workers: 5
|
||||
enable_ranker: true
|
||||
enable_today_contra_repeat: true
|
||||
enable_long_contra_repeat: false
|
||||
output_memory_max_count: 20
|
||||
|
||||
memory_chat:
|
||||
cli_memory_chat:
|
||||
class: core.chat.cli_memory_chat
|
||||
memory_service: memoryscope_service
|
||||
generation_model: generation_model
|
||||
stream: true
|
||||
|
||||
memory_service:
|
||||
memoryscope_service:
|
||||
class: core.service.memory_scope_service
|
||||
human_name: 用户
|
||||
assistant_name: AI
|
||||
memory_operations:
|
||||
read_message:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: read_message
|
||||
description: "read short memory"
|
||||
|
||||
retrieve_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,[extract_time|retrieve_obs_ins,semantic_rank],fuse_rerank
|
||||
description: "retrieve long-term memory"
|
||||
|
||||
list_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_top_memory,print_memory
|
||||
description: "read all long-term memory of the user, use `refresh_time=5` to refresh screen every 5 seconds."
|
||||
|
||||
delete_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_memory
|
||||
description: "delete a single long-term memory"
|
||||
|
||||
delete_all:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: set_query,retrieve_all_memory,delete_all
|
||||
description: "delete all long-term memory"
|
||||
|
||||
add_memory:
|
||||
class: core.operation.frontend_operation
|
||||
workflow: add_memory
|
||||
description: "add a single observation"
|
||||
|
||||
consolidate_memory:
|
||||
class: core.operation.consolidate_memory_op
|
||||
workflow: info_filter,[get_observation|get_observation_with_time|load_today_memory],contra_repeat,store_memory
|
||||
description: "summary user's observation memory, run backend."
|
||||
interval_time: 1
|
||||
|
||||
reflect_and_reconsolidate:
|
||||
class: core.operation.backend_operation
|
||||
workflow: load_obs_and_insight,get_reflection_subject,update_insight,long_contra_repeat,store_memory
|
||||
description: "summary user's insight memory, run backend."
|
||||
interval_time: 15
|
||||
|
||||
worker:
|
||||
dummy:
|
||||
class: core.worker.dummy_worker
|
||||
generation_model: generation_model
|
||||
embedding_model: embedding_model
|
||||
rank_model: rank_model
|
||||
read_message:
|
||||
class: core.worker.frontend.read_message_worker
|
||||
set_query:
|
||||
class: core.worker.frontend.set_query_worker
|
||||
retrieve_obs_ins:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
extract_time:
|
||||
class: core.worker.frontend.extract_time_worker
|
||||
generation_model: generation_model
|
||||
semantic_rank:
|
||||
class: core.worker.frontend.semantic_rank_worker
|
||||
rank_model: rank_model
|
||||
fuse_rerank:
|
||||
class: core.worker.frontend.fuse_rerank_worker
|
||||
fuse_score_threshold: 0.01
|
||||
fuse_ratio_dict:
|
||||
conversation: 0.5
|
||||
observation: 1
|
||||
obs_customized: 1.2
|
||||
insight: 2.0
|
||||
fuse_time_ratio: 2.0
|
||||
retrieve_top_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 100
|
||||
retrieve_ins_top_k: 100
|
||||
retrieve_expired_top_k: 100
|
||||
print_memory:
|
||||
class: core.worker.frontend.print_memory_worker
|
||||
retrieve_all_memory:
|
||||
class: core.worker.frontend.retrieve_memory_worker
|
||||
retrieve_obs_top_k: 1000
|
||||
retrieve_ins_top_k: 1000
|
||||
retrieve_expired_top_k: 1000
|
||||
delete_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_memory
|
||||
delete_all:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: delete_all
|
||||
add_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_query
|
||||
info_filter:
|
||||
class: core.worker.backend.info_filter_worker
|
||||
generation_model: generation_model
|
||||
load_today_memory:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_today_top_k: 100
|
||||
get_observation:
|
||||
class: core.worker.backend.get_observation_worker
|
||||
generation_model: generation_model
|
||||
get_observation_with_time:
|
||||
class: core.worker.backend.get_observation_with_time_worker
|
||||
generation_model: generation_model
|
||||
contra_repeat:
|
||||
class: core.worker.backend.contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
store_memory:
|
||||
class: core.worker.backend.update_memory_worker
|
||||
method: from_memory_key
|
||||
memory_key: all
|
||||
load_obs_and_insight:
|
||||
class: core.worker.backend.load_memory_worker
|
||||
retrieve_not_reflected_top_k: 100
|
||||
retrieve_not_updated_top_k: 100
|
||||
retrieve_insight_top_k: 100
|
||||
get_reflection_subject:
|
||||
class: core.worker.backend.get_reflection_subject_worker
|
||||
generation_model: generation_model
|
||||
reflect_obs_cnt_threshold: 6
|
||||
update_insight:
|
||||
class: core.worker.backend.update_insight_worker
|
||||
generation_model: generation_model
|
||||
rank_model: rank_model
|
||||
embedding_model: embedding_model
|
||||
update_insight_threshold: 0.01
|
||||
enable_parallel: false
|
||||
long_contra_repeat:
|
||||
class: core.worker.backend.long_contra_repeat_worker
|
||||
generation_model: generation_model
|
||||
long_contra_repeat_threshold: 0.5
|
||||
|
||||
model:
|
||||
generation_model:
|
||||
class: core.models.llama_index_generation_model
|
||||
module_name: dashscope_generation
|
||||
model_name: qwen-max
|
||||
max_tokens: 2000
|
||||
temperature: 0.01
|
||||
embedding_model:
|
||||
class: core.models.llama_index_embedding_model
|
||||
module_name: dashscope_embedding
|
||||
model_name: text-embedding-v2
|
||||
rank_model:
|
||||
class: core.models.llama_index_rank_model
|
||||
module_name: dashscope_rank
|
||||
model_name: gte-rerank
|
||||
top_n: 500
|
||||
|
||||
memory_store:
|
||||
class: core.storage.llama_index_es_memory_store
|
||||
embedding_model: embedding_model
|
||||
index_name: memory_index
|
||||
es_url: http://localhost:9200
|
||||
retrieve_mode: dense
|
||||
|
||||
monitor:
|
||||
class: core.storage.dummy_monitor
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
|
||||
from memoryscope.core.chat.base_memory_chat import BaseMemoryChat
|
||||
from memoryscope.core.config.config_manager import ConfigManager
|
||||
from memoryscope.core.memoryscope_context import MemoryscopeContext
|
||||
from memoryscope.core.service.base_memory_service import BaseMemoryService
|
||||
from memoryscope.core.utils.tool_functions import init_instance_by_config
|
||||
from memoryscope.enumeration.language_enum import LanguageEnum
|
||||
from memoryscope.enumeration.model_enum import ModelEnum
|
||||
|
||||
|
||||
class MemoryScope(ConfigManager):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._context: MemoryscopeContext = MemoryscopeContext()
|
||||
self._context.memory_scope_uuid = datetime.now().strftime(r"%Y%m%d_%H%M%S")
|
||||
super().__init__(**kwargs)
|
||||
self._init_context_by_config()
|
||||
|
||||
def _init_context_by_config(self):
|
||||
# set global config
|
||||
global_conf = self.config["global"]
|
||||
self._context.language = LanguageEnum(global_conf["language"])
|
||||
self._context.thread_pool = ThreadPoolExecutor(max_workers=global_conf["thread_pool_max_workers"])
|
||||
self._context.meta_data.update({
|
||||
"enable_ranker": global_conf["enable_ranker"],
|
||||
"enable_today_contra_repeat": global_conf["enable_today_contra_repeat"],
|
||||
"enable_long_contra_repeat": global_conf["enable_long_contra_repeat"],
|
||||
"output_memory_max_count": global_conf["output_memory_max_count"],
|
||||
})
|
||||
|
||||
if not global_conf["enable_ranker"]:
|
||||
self.logger.warning("If a semantic ranking model is not available, MemoryScope will use cosine similarity "
|
||||
"scoring as a substitute. However, the ranking effectiveness will be somewhat "
|
||||
"compromised.")
|
||||
|
||||
# init memory_chat
|
||||
memory_chat_conf_dict = self.config["memory_chat"]
|
||||
if memory_chat_conf_dict:
|
||||
for name, conf in memory_chat_conf_dict.items():
|
||||
self._context.memory_chat_dict[name] = init_instance_by_config(conf, name=name, context=self._context)
|
||||
|
||||
# set memory_service
|
||||
memory_service_conf_dict = self.config["memory_service"]
|
||||
assert memory_service_conf_dict
|
||||
for name, conf in memory_service_conf_dict.items():
|
||||
self._context.memory_service_dict[name] = init_instance_by_config(conf, name=name, context=self._context)
|
||||
|
||||
# init model
|
||||
model_conf_dict = self.config["model"]
|
||||
assert model_conf_dict
|
||||
for name, conf in model_conf_dict.items():
|
||||
self._context.model_dict[name] = init_instance_by_config(conf, name=name)
|
||||
|
||||
# init memory_store
|
||||
memory_store_conf = self.config["memory_store"]
|
||||
assert memory_store_conf
|
||||
emb_model_name: str = memory_store_conf[ModelEnum.EMBEDDING_MODEL.value]
|
||||
embedding_model = self._context.model_dict[emb_model_name]
|
||||
self._context.memory_store = init_instance_by_config(memory_store_conf, embedding_model=embedding_model)
|
||||
|
||||
# init monitor
|
||||
monitor_conf = self.config["monitor"]
|
||||
if monitor_conf:
|
||||
self._context.monitor = init_instance_by_config(monitor_conf)
|
||||
|
||||
# set worker config
|
||||
self._context.worker_conf_dict = self.config["worker"]
|
||||
|
||||
def close(self):
|
||||
# wait service to stop
|
||||
for _, service in self._context.memory_service_dict.items():
|
||||
service.stop_backend_service(wait_service=True)
|
||||
|
||||
self._context.thread_pool.shutdown()
|
||||
|
||||
self._context.memory_store.close()
|
||||
|
||||
if self._context.monitor:
|
||||
self._context.monitor.close()
|
||||
|
||||
self.logger.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is not None:
|
||||
self.logger.warning(f"An exception occurred: {exc_type.__name__}: {exc_val}\n{exc_tb}")
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._context
|
||||
|
||||
@property
|
||||
def memory_chat_dict(self):
|
||||
return self._context.memory_chat_dict
|
||||
|
||||
@property
|
||||
def memory_service_dict(self):
|
||||
return self._context.memory_service_dict
|
||||
|
||||
@property
|
||||
def default_memory_chat(self) -> BaseMemoryChat:
|
||||
return list(self.memory_chat_dict.values())[0]
|
||||
|
||||
@property
|
||||
def default_memory_service(self) -> BaseMemoryService:
|
||||
return list(self.memory_service_dict.values())[0]
|
||||
|
||||
@classmethod
|
||||
def cli_memory_chat(cls, **kwargs):
|
||||
with cls(**kwargs) as ms:
|
||||
memory_chat = ms.default_memory_chat
|
||||
memory_chat.run()
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from memoryscope.enumeration.language_enum import LanguageEnum
|
||||
from memoryscope.core.utils.singleton import singleton
|
||||
|
||||
@singleton
|
||||
@dataclass
|
||||
class MemoryscopeContext(object):
|
||||
"""
|
||||
The context class archives all configs utilized by store, monitor, services and workers.
|
||||
"""
|
||||
|
||||
language: LanguageEnum = LanguageEnum.EN
|
||||
|
||||
thread_pool: ThreadPoolExecutor | None = None
|
||||
|
||||
memory_store = None
|
||||
|
||||
monitor = None
|
||||
|
||||
memory_chat_dict: dict = field(default_factory=lambda: {}, metadata={"help": "name -> memory_chat"})
|
||||
|
||||
memory_service_dict: dict = field(default_factory=lambda: {}, metadata={"help": "name -> memory_service"})
|
||||
|
||||
model_dict: dict = field(default_factory=lambda: {}, metadata={"help": "name -> model"})
|
||||
|
||||
worker_conf_dict: dict = field(default_factory=lambda: {}, metadata={"help": "name -> worker_conf"})
|
||||
|
||||
meta_data: dict = field(default_factory=lambda: {})
|
||||
|
||||
memory_scope_uuid: str = ""
|
||||
|
||||
print_workflow_dynamic: bool = False
|
||||
|
||||
log_elasticsearch_dynamic: bool = False
|
||||
|
||||
|
||||
def get_memoryscope_uuid():
|
||||
ms_context = MemoryscopeContext()
|
||||
if ms_context.memory_scope_uuid:
|
||||
return ms_context.memory_scope_uuid
|
||||
else:
|
||||
# raise RuntimeError("MemoryscopeContext is not initialized yet. Please initialize it first.")
|
||||
return "memory_scope_uuid_not_registered"
|
||||
|
||||
def get_memoryscope_context():
|
||||
ms_context = MemoryscopeContext()
|
||||
if ms_context.memory_scope_uuid:
|
||||
return ms_context
|
||||
else:
|
||||
# raise RuntimeError("MemoryscopeContext is not initialized yet. Please initialize it first.")
|
||||
return "memory_scope_uuid_not_registered"
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from .base_model import BaseModel
|
||||
from .dummy_generation_model import DummyGenerationModel
|
||||
from .llama_index_embedding_model import LlamaIndexEmbeddingModel
|
||||
from .llama_index_generation_model import LlamaIndexGenerationModel
|
||||
from .llama_index_rank_model import LlamaIndexRankModel
|
||||
|
||||
__all__ = [
|
||||
"BaseModel",
|
||||
"DummyGenerationModel",
|
||||
"LlamaIndexEmbeddingModel",
|
||||
"LlamaIndexGenerationModel",
|
||||
"LlamaIndexRankModel"
|
||||
]
|
||||