Compare commits

...

7 Commits

Author SHA1 Message Date
bbe16046c8 feat: 添加 ffmpeg
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m31s
2025-12-14 22:07:46 +08:00
8a4719e6ac chore: 添加本地构建配置 2025-12-14 22:07:40 +08:00
Windpicker-owo
dd0dd94e76 Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox-Core into dev 2025-12-14 19:09:35 +08:00
Windpicker-owo
3207aa31b1 删除内存分析工具使用指南文档 2025-12-14 19:09:08 +08:00
LuiKlee
6de5cd9902 短期记忆补丁 2025-12-14 14:12:39 +08:00
tt-P607
1ad9c932bb Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox-Core into dev 2025-12-14 13:45:35 +08:00
tt-P607
8f2a6606eb feat(social-toolkit):优化戳一戳动作逻辑和使用规则
更新 PokeAction 配置以实施更严格的使用指南:
- 区分私聊和群聊的行为(在群聊中需克制)。
- 添加频率限制和最大戳一戳次数。
- 扩展 LLM 判断提示,包含上下文、情绪和用户明确请求的详细规则
2025-12-14 13:45:32 +08:00
11 changed files with 155 additions and 770 deletions

View File

@@ -0,0 +1,32 @@
name: Build and Push Docker Image
on:
push:
branches:
- dev
- gitea
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: docker.gardel.top
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: docker.gardel.top/gardel/mofox:dev
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}

View File

@@ -1,149 +0,0 @@
name: Docker Build and Push
on:
push:
branches:
- master
- dev
tags:
- "v*.*.*"
- "v*"
- "*.*.*"
- "*.*.*-*"
workflow_dispatch: # 允许手动触发工作流
# Workflow's jobs
jobs:
build-amd64:
name: Build AMD64 Image
runs-on: ubuntu-24.04
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Check out git repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug
# Log in docker hub
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mofox
# Build and push AMD64 image by digest
- name: Build and push AMD64
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
file: ./Dockerfile
cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/mofox:amd64-buildcache
cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/mofox:amd64-buildcache,mode=max
outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/mofox,push-by-digest=true,name-canonical=true,push=true
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}
build-arm64:
name: Build ARM64 Image
runs-on: ubuntu-24.04-arm
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Check out git repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug
# Log in docker hub
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mofox
# Build and push ARM64 image by digest
- name: Build and push ARM64
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64/v8
labels: ${{ steps.meta.outputs.labels }}
file: ./Dockerfile
cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/mofox:arm64-buildcache
cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/mofox:arm64-buildcache,mode=max
outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/mofox,push-by-digest=true,name-canonical=true,push=true
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}
create-manifest:
name: Create Multi-Arch Manifest
runs-on: ubuntu-24.04
needs:
- build-amd64
- build-arm64
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Log in docker hub
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Generate metadata for Docker images
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mofox
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=${{ github.ref_name }}-,enable=${{ github.ref_type == 'branch' }}
- name: Create and Push Manifest
run: |
# 为每个标签创建多架构镜像
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' '); do
echo "Creating manifest for $tag"
docker buildx imagetools create -t $tag \
${{ secrets.DOCKERHUB_USERNAME }}/mofox@${{ needs.build-amd64.outputs.digest }} \
${{ secrets.DOCKERHUB_USERNAME }}/mofox@${{ needs.build-arm64.outputs.digest }}
done

View File

@@ -1,102 +0,0 @@
# AWS Bedrock 集成完成 ✅
## 快速开始
### 1. 安装依赖
```bash
pip install aioboto3 botocore
```
### 2. 配置凭证
`config/model_config.toml` 添加:
```toml
[[api_providers]]
name = "bedrock_us_east"
base_url = ""
api_key = "YOUR_AWS_ACCESS_KEY_ID"
client_type = "bedrock"
timeout = 60
[api_providers.extra_params]
aws_secret_key = "YOUR_AWS_SECRET_ACCESS_KEY"
region = "us-east-1"
[[models]]
model_identifier = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"
name = "claude-3.5-sonnet-bedrock"
api_provider = "bedrock_us_east"
price_in = 3.0
price_out = 15.0
```
### 3. 使用示例
```python
from src.llm_models import get_llm_client
from src.llm_models.payload_content.message import MessageBuilder
client = get_llm_client("bedrock_us_east")
builder = MessageBuilder()
builder.add_user_message("你好AWS Bedrock")
response = await client.get_response(
model_info=get_model_info("claude-3.5-sonnet-bedrock"),
message_list=[builder.build()],
max_tokens=1024
)
print(response.content)
```
## 新增文件
-`src/llm_models/model_client/bedrock_client.py` - Bedrock 客户端实现
-`docs/integrations/Bedrock.md` - 完整文档
-`scripts/test_bedrock_client.py` - 测试脚本
## 修改文件
-`src/llm_models/model_client/__init__.py` - 添加 Bedrock 导入
-`src/config/api_ada_configs.py` - 添加 `bedrock` client_type
-`template/model_config_template.toml` - 添加 Bedrock 配置示例(注释形式)
-`requirements.txt` - 添加 aioboto3 和 botocore 依赖
-`pyproject.toml` - 添加 aioboto3 和 botocore 依赖
## 支持功能
-**对话生成**:支持多轮对话
-**流式输出**:支持流式响应
-**工具调用**:完整支持 Tool Use
-**多模态**:支持图片输入
-**文本嵌入**:支持 Titan Embeddings
-**跨区推理**:支持 Inference Profile
## 支持模型
- Amazon Nova 系列 (Micro/Lite/Pro)
- Anthropic Claude 3/3.5 系列
- Meta Llama 2/3 系列
- Mistral AI 系列
- Cohere Command 系列
- AI21 Jamba 系列
- Stability AI SDXL
## 测试
```bash
# 修改凭证后运行测试
python scripts/test_bedrock_client.py
```
## 文档
详细文档:`docs/integrations/Bedrock.md`
---
**集成状态**: ✅ 生产就绪
**集成时间**: 2025年12月6日

View File

@@ -9,6 +9,10 @@ RUN apt-get update && apt-get install -y build-essential
# 复制依赖列表和锁文件
COPY pyproject.toml uv.lock ./
COPY --from=mwader/static-ffmpeg:latest /ffmpeg /usr/local/bin/ffmpeg
COPY --from=mwader/static-ffmpeg:latest /ffprobe /usr/local/bin/ffprobe
RUN ldconfig && ffmpeg -version
# 安装依赖(使用 --frozen 确保使用锁文件中的版本)
RUN uv sync --frozen --no-dev

View File

@@ -1,471 +0,0 @@
# Bot 内存分析工具使用指南
一个统一的内存诊断工具,提供进程监控、对象分析和数据可视化功能。
## 🚀 快速开始
> **提示**: 建议使用虚拟环境运行脚本(`.\.venv\Scripts\python.exe`
```powershell
# 查看帮助
.\.venv\Scripts\python.exe scripts/memory_profiler.py --help
# 进程监控模式(最简单)
.\.venv\Scripts\python.exe scripts/memory_profiler.py --monitor
# 对象分析模式(深度分析)
.\.venv\Scripts\python.exe scripts/memory_profiler.py --objects --output memory_data.txt
# 可视化模式(生成图表)
.\.venv\Scripts\python.exe scripts/memory_profiler.py --visualize --input memory_data.txt.jsonl
```
**或者使用简短命令**(如果你的系统 `python` 已指向虚拟环境):
```powershell
python scripts/memory_profiler.py --monitor
```
## 📦 依赖安装
```powershell
# 基础功能(进程监控)
pip install psutil
# 对象分析功能
pip install pympler
# 可视化功能
pip install matplotlib
# 一次性安装全部
pip install psutil pympler matplotlib
```
## 🔧 三种模式详解
### 1. 进程监控模式 (--monitor)
**用途**: 从外部监控 bot 进程的总内存、子进程情况
**特点**:
- ✅ 自动启动 bot.py使用虚拟环境
- ✅ 实时显示进程内存RSS、VMS
- ✅ 列出所有子进程及其内存占用
- ✅ 显示 bot 输出日志
- ✅ 自动保存监控历史
**使用示例**:
```powershell
# 基础用法
python scripts/memory_profiler.py --monitor
# 自定义监控间隔10秒
python scripts/memory_profiler.py --monitor --interval 10
# 简写
python scripts/memory_profiler.py -m -i 5
```
**输出示例**:
```
================================================================================
检查点 #1 - 14:23:15
Bot 进程 (PID: 12345)
RSS: 45.82 MB
VMS: 12.34 MB
占比: 0.25%
子进程: 2 个
子进程内存: 723.64 MB
总内存: 769.46 MB
📋 子进程详情:
[1] PID 12346: python.exe - 520.15 MB
命令: python.exe -m chromadb.server ...
[2] PID 12347: python.exe - 203.49 MB
命令: python.exe -m uvicorn ...
================================================================================
```
**保存位置**: `data/memory_diagnostics/process_monitor_<timestamp>_pid<PID>.txt`
---
### 2. 对象分析模式 (--objects)
**用途**: 在 bot 进程内部统计所有 Python 对象的内存占用
**特点**:
- ✅ 统计所有对象类型dict、list、str、AsyncOpenAI 等)
-**按模块统计内存占用(新增)** - 显示哪个模块占用最多内存
- ✅ 包含所有线程的对象
- ✅ 显示对象变化diff
- ✅ 线程信息和 GC 统计
- ✅ 保存 JSONL 数据用于可视化
**使用示例**:
```powershell
# 基础用法(推荐指定输出文件)
python scripts/memory_profiler.py --objects --output memory_data.txt
# 自定义参数
python scripts/memory_profiler.py --objects \
--interval 10 \
--output memory_data.txt \
--object-limit 30
# 简写
python scripts/memory_profiler.py -o -i 10 --output data.txt -l 30
```
**输出示例**:
```
================================================================================
🔍 对象级内存分析 #1 - 14:25:30
================================================================================
📦 对象统计 (前 20 个类型):
类型 数量 总大小
--------------------------------------------------------------------------------
<class 'dict'> 125,843 45.23 MB
<class 'str'> 234,567 23.45 MB
<class 'list'> 56,789 12.34 MB
<class 'tuple'> 89,012 8.90 MB
<class 'openai.resources.chat.completions'> 12 5.67 MB
...
📚 模块内存占用 (前 20 个模块):
模块名 对象数 总内存
--------------------------------------------------------------------------------
builtins 169,144 26.20 MB
src 12,345 5.67 MB
openai 3,456 2.34 MB
chromadb 2,345 1.89 MB
...
总模块数: 85
🧵 线程信息 (8 个):
[1] ✓ MainThread
[2] ✓ AsyncOpenAIClient (守护)
[3] ✓ ChromaDBWorker (守护)
...
🗑️ 垃圾回收:
代 0: 1,234 次
代 1: 56 次
代 2: 3 次
追踪对象: 456,789
📊 总对象数: 567,890
================================================================================
```
**每 3 次迭代会显示对象变化**:
```
📈 对象变化分析:
--------------------------------------------------------------------------------
types | # objects | total size
==================== | =========== | ============
<class 'dict'> | +1234 | +1.23 MB
<class 'str'> | +567 | +0.56 MB
...
--------------------------------------------------------------------------------
```
**保存位置**:
- 文本: `<output>.txt`
- 结构化数据: `<output>.txt.jsonl`
---
### 3. 可视化模式 (--visualize)
**用途**: 将对象分析模式生成的 JSONL 数据绘制成图表
**特点**:
- ✅ 显示对象类型随时间的内存变化
- ✅ 自动选择内存占用最高的 N 个类型
- ✅ 生成高清 PNG 图表
**使用示例**:
```powershell
# 基础用法
python scripts/memory_profiler.py --visualize \
--input memory_data.txt.jsonl
# 自定义参数
python scripts/memory_profiler.py --visualize \
--input memory_data.txt.jsonl \
--top 15 \
--plot-output my_plot.png
# 简写
python scripts/memory_profiler.py -v -i data.txt.jsonl -t 15
```
**输出**: PNG 图像,展示前 N 个对象类型的内存占用随时间的变化曲线
**保存位置**: 默认 `memory_analysis_plot.png`,可通过 `--plot-output` 指定
---
## 💡 使用场景
| 场景 | 推荐模式 | 命令 |
|------|----------|------|
| 快速查看总内存 | `--monitor` | `python scripts/memory_profiler.py -m` |
| 查看子进程占用 | `--monitor` | `python scripts/memory_profiler.py -m` |
| 分析具体对象占用 | `--objects` | `python scripts/memory_profiler.py -o --output data.txt` |
| 追踪内存泄漏 | `--objects` | `python scripts/memory_profiler.py -o --output data.txt` |
| 可视化分析趋势 | `--visualize` | `python scripts/memory_profiler.py -v -i data.txt.jsonl` |
## 📊 完整工作流程
### 场景 1: 快速诊断内存问题
```powershell
# 1. 运行进程监控(查看总体情况)
python scripts/memory_profiler.py --monitor --interval 5
# 观察输出,如果发现内存异常,进入场景 2
```
### 场景 2: 深度分析对象占用
```powershell
# 1. 启动对象分析(保存数据)
python scripts/memory_profiler.py --objects \
--interval 10 \
--output data/memory_diagnostics/analysis_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt
# 2. 运行一段时间(建议至少 5-10 分钟),按 Ctrl+C 停止
# 3. 生成可视化图表
python scripts/memory_profiler.py --visualize \
--input data/memory_diagnostics/analysis_<timestamp>.txt.jsonl \
--top 15 \
--plot-output data/memory_diagnostics/plot_<timestamp>.png
# 4. 查看图表,分析哪些对象类型随时间增长
```
### 场景 3: 持续监控
```powershell
# 在后台运行对象分析Windows
Start-Process powershell -ArgumentList "-Command", "python scripts/memory_profiler.py -o -i 30 --output logs/memory_continuous.txt" -WindowStyle Minimized
# 定期查看 JSONL 并生成图表
python scripts/memory_profiler.py -v -i logs/memory_continuous.txt.jsonl -t 20
```
## 🎯 参数参考
### 通用参数
| 参数 | 简写 | 默认值 | 说明 |
|------|------|--------|------|
| `--interval` | `-i` | 10 | 监控间隔(秒) |
### 对象分析模式参数
| 参数 | 简写 | 默认值 | 说明 |
|------|------|--------|------|
| `--output` | - | 无 | 输出文件路径(强烈推荐) |
| `--object-limit` | `-l` | 20 | 显示的对象类型数量 |
### 可视化模式参数
| 参数 | 简写 | 默认值 | 说明 |
|------|------|--------|------|
| `--input` | - | **必需** | 输入 JSONL 文件路径 |
| `--top` | `-t` | 10 | 展示前 N 个对象类型 |
| `--plot-output` | - | `memory_analysis_plot.png` | 输出图表路径 |
## ⚠️ 注意事项
### 性能影响
| 模式 | 性能影响 | 说明 |
|------|----------|------|
| `--monitor` | < 1% | 几乎无影响适合生产环境 |
| `--objects` | 5-15% | 有一定影响建议在测试环境使用 |
| `--visualize` | 0% | 离线分析无影响 |
### 常见问题
**Q: 对象分析模式报错 "pympler 未安装"**
```powershell
pip install pympler
```
**Q: 可视化模式报错 "matplotlib 未安装"**
```powershell
pip install matplotlib
```
**Q: 对象分析模式提示 "bot.py 未找到 main_async() 或 main() 函数"**
这是正常的如果你的 bot.py 的主逻辑在 `if __name__ == "__main__":` 监控线程仍会在后台运行你可以
- 保持 bot 运行监控会持续统计
- 或者在 bot.py 中添加一个 `main_async()` `main()` 函数
**Q: 进程监控模式看不到子进程?**
确保 bot.py 已经启动了子进程例如 ChromaDB)。如果刚启动就查看可能还没有创建子进程
**Q: JSONL 文件在哪里?**
当你使用 `--output <file>` 会生成
- `<file>`: 人类可读的文本
- `<file>.jsonl`: 结构化数据用于可视化
## 📁 输出文件说明
### 进程监控输出
**位置**: `data/memory_diagnostics/process_monitor_<timestamp>_pid<PID>.txt`
**内容**: 每次检查点的进程内存信息
### 对象分析输出
**文本文件**: `<output>`
- 人类可读格式
- 包含每次迭代的对象统计
**JSONL 文件**: `<output>.jsonl`
- 每行一个 JSON 对象
- 包含: timestamp, iteration, total_objects, summary, threads, gc_stats
- 用于可视化分析
### 可视化输出
**PNG 图像**: 默认 `memory_analysis_plot.png`
- 折线图展示对象类型随时间的内存变化
- 高清 150 DPI
## 🔍 诊断技巧
### 1. 识别内存泄漏
使用对象分析模式运行较长时间观察
- 某个对象类型的数量或大小持续增长
- 对象变化 diff 中始终为正数
### 2. 定位大内存对象
**查看对象统计**:
- 如果 `<class 'dict'>` 占用很大可能是缓存未清理
- 如果看到特定类 `AsyncOpenAI`检查该类的实例数
**查看模块统计**推荐:
- 查看 📚 模块内存占用部分
- 如果 `src` 模块占用很大说明你的代码中有大量对象
- 如果 `openai``chromadb` 等第三方模块占用大可能是这些库的使用问题
- 对比不同时间点看哪个模块的内存持续增长
### 3. 分析子进程占用
使用进程监控模式
- 查看子进程详情中的命令行
- 识别哪个子进程占用大量内存 ChromaDB
### 4. 对比不同时间点
使用可视化模式
- 生成图表后观察哪些对象类型的曲线持续上升
- 对比不同功能运行时的内存变化
## 🎓 高级用法
### 长期监控脚本
创建 `monitor_continuously.ps1`:
```powershell
# 持续监控脚本
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logPath = "logs/memory_analysis_$timestamp.txt"
Write-Host "开始持续监控,数据保存到: $logPath"
Write-Host "按 Ctrl+C 停止监控"
python scripts/memory_profiler.py --objects --interval 30 --output $logPath
```
### 自动生成日报
创建 `generate_daily_report.ps1`:
```powershell
# 生成内存分析日报
$date = Get-Date -Format "yyyyMMdd"
$jsonlFiles = Get-ChildItem "logs" -Filter "*$date*.jsonl"
foreach ($file in $jsonlFiles) {
$outputPlot = $file.FullName -replace ".jsonl", "_plot.png"
python scripts/memory_profiler.py --visualize --input $file.FullName --plot-output $outputPlot --top 20
Write-Host "生成图表: $outputPlot"
}
```
## 📚 扩展阅读
- **Python 内存管理**: https://docs.python.org/3/c-api/memory.html
- **psutil 文档**: https://psutil.readthedocs.io/
- **Pympler 文档**: https://pympler.readthedocs.io/
- **Matplotlib 文档**: https://matplotlib.org/
## 🆘 获取帮助
```powershell
# 查看完整帮助信息
python scripts/memory_profiler.py --help
# 查看特定模式示例
python scripts/memory_profiler.py --help | Select-String "示例"
```
---
**快速开始提醒**:
```powershell
# 使用虚拟环境(推荐)
.\.venv\Scripts\python.exe scripts/memory_profiler.py --monitor
# 或者使用系统 Python
python scripts/memory_profiler.py --monitor
# 深度分析
.\.venv\Scripts\python.exe scripts/memory_profiler.py --objects --output memory.txt
# 可视化
.\.venv\Scripts\python.exe scripts/memory_profiler.py --visualize --input memory.txt.jsonl
```
### 💡 虚拟环境说明
**Windows**:
```powershell
.\.venv\Scripts\python.exe scripts/memory_profiler.py [选项]
```
**Linux/Mac**:
```bash
./.venv/bin/python scripts/memory_profiler.py [选项]
```
脚本会自动检测并使用项目虚拟环境来启动 bot进程监控模式对象分析模式会自动添加项目根目录到 Python 路径
🎉 现在你已经掌握了完整的内存分析工具

View File

@@ -121,7 +121,7 @@ async def conversation_loop(
except asyncio.CancelledError:
logger.info(f" [生成器] stream={stream_id[:8]}, 被取消")
break
except Exception as e: # noqa: BLE001
except Exception as e:
logger.error(f" [生成器] stream={stream_id[:8]}, 出错: {e}")
await asyncio.sleep(5.0)
@@ -151,10 +151,10 @@ async def run_chat_stream(
# 创建生成器
tick_generator = conversation_loop(
stream_id=stream_id,
get_context_func=manager._get_stream_context, # noqa: SLF001
calculate_interval_func=manager._calculate_interval, # noqa: SLF001
flush_cache_func=manager._flush_cached_messages_to_unread, # noqa: SLF001
check_force_dispatch_func=manager._needs_force_dispatch_for_context, # noqa: SLF001
get_context_func=manager._get_stream_context,
calculate_interval_func=manager._calculate_interval,
flush_cache_func=manager._flush_cached_messages_to_unread,
check_force_dispatch_func=manager._needs_force_dispatch_for_context,
is_running_func=lambda: manager.is_running,
)
@@ -162,13 +162,13 @@ async def run_chat_stream(
async for tick in tick_generator:
try:
# 获取上下文
context = await manager._get_stream_context(stream_id) # noqa: SLF001
context = await manager._get_stream_context(stream_id)
if not context:
continue
# 并发保护:检查是否正在处理
if context.is_chatter_processing:
if manager._recover_stale_chatter_state(stream_id, context): # noqa: SLF001
if manager._recover_stale_chatter_state(stream_id, context):
logger.warning(f" [驱动器] stream={stream_id[:8]}, 处理标志残留已修复")
else:
logger.debug(f" [驱动器] stream={stream_id[:8]}, Chatter正在处理跳过此Tick")
@@ -182,7 +182,7 @@ async def run_chat_stream(
# 更新能量值
try:
await manager._update_stream_energy(stream_id, context) # noqa: SLF001
await manager._update_stream_energy(stream_id, context)
except Exception as e:
logger.debug(f"更新能量失败: {e}")
@@ -191,7 +191,7 @@ async def run_chat_stream(
try:
async with manager._processing_semaphore:
success = await asyncio.wait_for(
manager._process_stream_messages(stream_id, context), # noqa: SLF001
manager._process_stream_messages(stream_id, context),
global_config.chat.thinking_timeout,
)
except asyncio.TimeoutError:
@@ -209,7 +209,7 @@ async def run_chat_stream(
except asyncio.CancelledError:
raise
except Exception as e: # noqa: BLE001
except Exception as e:
logger.error(f" [驱动器] stream={stream_id[:8]}, 处理Tick时出错: {e}")
manager.stats["total_failures"] += 1
@@ -222,7 +222,7 @@ async def run_chat_stream(
if context and context.stream_loop_task:
context.stream_loop_task = None
logger.debug(f" [驱动器] stream={stream_id[:8]}, 清理任务记录")
except Exception as e: # noqa: BLE001
except Exception as e:
logger.debug(f"清理任务记录失败: {e}")

View File

@@ -110,10 +110,10 @@ class MessageManager:
if not (context.stream_loop_task and not context.stream_loop_task.done()):
# 异步启动驱动器任务;避免在高并发下阻塞消息入队
await stream_loop_manager.start_stream_loop(stream_id)
# 检查并处理消息打断
await self._check_and_handle_interruption(chat_stream, message)
# 入队消息
await chat_stream.context.add_message(message)

View File

@@ -1,8 +1,8 @@
import os
import shutil
import sys
import typing
import types
import typing
from datetime import datetime
from pathlib import Path
from typing import Any, get_args, get_origin
@@ -30,8 +30,8 @@ from src.config.official_configs import (
ExperimentalConfig,
ExpressionConfig,
InnerConfig,
LogConfig,
KokoroFlowChatterConfig,
LogConfig,
LPMMKnowledgeConfig,
MemoryConfig,
MessageBusConfig,
@@ -515,7 +515,7 @@ class Config(ValidatedConfigBase):
)
@property
def MMC_VERSION(self) -> str: # noqa: N802
def MMC_VERSION(self) -> str:
return MMC_VERSION

View File

@@ -66,6 +66,13 @@ class LongTermMemoryManager:
self._similar_memory_cache: dict[str, list[Memory]] = {}
self._cache_max_size = 100
# 错误/重试统计与配置
self._max_process_retries = 2
self._retry_backoff = 0.5
self._total_processed = 0
self._failed_single_memory_count = 0
self._retry_attempts = 0
logger.info(
f"长期记忆管理器已创建 (batch_size={batch_size}, "
f"search_top_k={search_top_k}, decay_factor={long_term_decay_factor:.2f})"
@@ -202,6 +209,10 @@ class LongTermMemoryManager:
else:
result["failed_count"] += 1
# 更新全局计数
self._total_processed += result["processed_count"]
self._failed_single_memory_count += result["failed_count"]
# 处理完批次后批量生成embeddings
await self._flush_pending_embeddings()
@@ -217,26 +228,45 @@ class LongTermMemoryManager:
Returns:
处理结果或None如果失败
"""
try:
# 步骤1: 在长期记忆中检索相似记忆
similar_memories = await self._search_similar_long_term_memories(stm)
# 增加重试机制以应对 LLM/执行的临时失败
attempt = 0
last_exc: Exception | None = None
while attempt <= self._max_process_retries:
try:
# 步骤1: 在长期记忆中检索相似记忆
similar_memories = await self._search_similar_long_term_memories(stm)
# 步骤2: LLM 决策如何更新图结构
operations = await self._decide_graph_operations(stm, similar_memories)
# 步骤2: LLM 决策如何更新图结构
operations = await self._decide_graph_operations(stm, similar_memories)
# 步骤3: 执行图操作
success = await self._execute_graph_operations(operations, stm)
# 步骤3: 执行图操作
success = await self._execute_graph_operations(operations, stm)
if success:
return {
"success": True,
"operations": [op.operation_type for op in operations]
}
return None
if success:
return {
"success": True,
"operations": [op.operation_type for op in operations]
}
except Exception as e:
logger.error(f"处理短期记忆 {stm.id} 失败: {e}")
return None
# 如果执行返回 False视为一次失败准备重试
last_exc = RuntimeError("_execute_graph_operations 返回 False")
raise last_exc
except Exception as e:
last_exc = e
attempt += 1
if attempt <= self._max_process_retries:
self._retry_attempts += 1
backoff = self._retry_backoff * attempt
logger.warning(
f"处理短期记忆 {stm.id} 时发生可恢复错误,重试 {attempt}/{self._max_process_retries},等待 {backoff}s: {e}"
)
await asyncio.sleep(backoff)
continue
# 超过重试次数,记录失败并返回 None
logger.error(f"处理短期记忆 {stm.id} 最终失败: {last_exc}")
self._failed_single_memory_count += 1
return None
async def _search_similar_long_term_memories(
self, stm: ShortTermMemory

View File

@@ -648,15 +648,15 @@ class ShortTermMemoryManager:
else:
low_importance_memories.append(mem)
# 如果低重要性记忆数量超过了上限(说明积压严重)
# 我们需要清理掉一部分,而不是转移它们
if len(low_importance_memories) > self.max_memories:
# 如果总体记忆数量超过了上限,优先清理低重要性最早创建的记忆
if len(self.memories) > self.max_memories:
# 目标保留数量(降至上限的 90%
target_keep_count = int(self.max_memories * 0.9)
num_to_remove = len(low_importance_memories) - target_keep_count
# 需要删除的数量(从当前总数降到 target_keep_count
num_to_remove = len(self.memories) - target_keep_count
if num_to_remove > 0:
# 按创建时间排序,删除最早的
if num_to_remove > 0 and low_importance_memories:
# 按创建时间排序,删除最早的低重要性记忆
low_importance_memories.sort(key=lambda x: x.created_at)
to_remove = low_importance_memories[:num_to_remove]
@@ -664,7 +664,7 @@ class ShortTermMemoryManager:
remove_ids = {mem.id for mem in to_remove}
self.memories = [mem for mem in self.memories if mem.id not in remove_ids]
for mem_id in remove_ids:
del self._memory_id_index[mem_id]
self._memory_id_index.pop(mem_id, None)
self._similarity_cache.pop(mem_id, None)
logger.info(
@@ -675,6 +675,16 @@ class ShortTermMemoryManager:
# 触发保存
asyncio.create_task(self._save_to_disk())
# 优先返回高重要性候选
if candidates:
return candidates
# 如果没有高重要性候选但总体超过上限,返回按创建时间最早的低重要性记忆作为后备转移候选
if len(self.memories) > self.max_memories:
needed = len(self.memories) - self.max_memories + 1
low_importance_memories.sort(key=lambda x: x.created_at)
return low_importance_memories[:needed]
return candidates
async def clear_transferred_memories(self, memory_ids: list[str]) -> None:

View File

@@ -140,7 +140,7 @@ class PokeAction(BaseAction):
# === 基本信息(必须填写)===
action_name = "poke_user"
action_description = "可以让你戳其他用户,为互动增添一份小小的乐趣"
action_description = "戳一戳其他用户。这是一个需要谨慎使用的互动方式,默认只戳一次。群聊中应当克制使用,私聊中可以适当主动"
activation_type = ActionActivationType.ALWAYS
parallel_action = True
@@ -148,17 +148,48 @@ class PokeAction(BaseAction):
action_parameters: ClassVar[dict] = {
"user_name": "需要戳一戳的用户的名字 (可选)",
"user_id": "需要戳一戳的用户的ID (可选,优先级更高)",
"times": "需要戳一戳的次数 (默认为 1)",
"times": "需要戳一戳的次数 (默认为 1最多3次)",
}
action_require: ClassVar[list] = ["当需要戳某个用户时使用", "当你想提醒特定用户时使用"]
action_require: ClassVar[list] = [
"用户明确要求戳某人时必须使用",
"私聊场景:可以在适当的互动时机主动使用(如回应戳一戳、俏皮互动等)",
"群聊场景:应当非常克制,仅在用户明确要求或有充分理由时才使用",
]
llm_judge_prompt = """
判定是否需要使用戳一戳动作的条件:
1. **互动时机**: 这是一个有趣的互动方式,可以在想提醒某人,或者单纯想开个玩笑时使用。
2. **用户请求**: 当用户明确要求使用戳一戳时。
3. **上下文需求**: 当上下文明确需要你戳一个或多个人时
4. **频率与情绪**: 如果最近已经戳过,或者感觉对方情绪不高,请避免使用,不要打扰到别人哦。
请根据上述规则,回答“是”或“否”。
**必须遵守的严格规则:**
1. **用户明确要求**: 当用户明确说"戳XX""戳一下XX"等直接指令时,必须使用
2. **群聊场景(非常克制)**:
- 群聊中应当非常谨慎,避免在公共场合频繁打扰他人
- 仅在以下情况考虑使用:用户明确要求、需要紧急提醒某人、或有特别充分的互动理由
- 如果最近已经在群里戳过任何人,必须回答""
- 群聊中不要随意主动使用,除非有明确必要性
3. **私聊场景(可以主动)**:
- 私聊中可以更加主动和俏皮
- 在以下情况可以使用:回应对方的戳一戳、轻松愉快的互动氛围、想要增添趣味时
- 但如果最近已经戳过对方,应避免频繁使用
4. **频率限制(重要)**:
- 如果最近已经戳过同一个人,必须回答""
- 默认只戳一次,不要多次戳别人(除非用户明确要求多次)
- 注意对方的情绪反应,如果对方看起来不高兴或不想被打扰,必须回答""
5. **禁止情况**:
- 对方情绪低落、生气、不耐烦时,严禁使用
- 严肃的对话场景中,严禁使用
- 刚刚戳过的情况下,严禁再次使用
**判断逻辑**:
- 首先判断是群聊还是私聊
- 群聊:除非用户明确要求或有特殊必要性,否则回答""
- 私聊:可以在合适的互动氛围中主动使用,但要注意频率
- 检查是否最近已经戳过,如果是则回答""
- 评估对方情绪和对话氛围是否适合
请严格根据上述规则,仅回答""""
"""
associated_types: ClassVar[list[str]] = ["text"]