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

View File

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

View File

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

View File

@@ -648,15 +648,15 @@ class ShortTermMemoryManager:
else: else:
low_importance_memories.append(mem) low_importance_memories.append(mem)
# 如果低重要性记忆数量超过了上限(说明积压严重) # 如果总体记忆数量超过了上限,优先清理低重要性最早创建的记忆
# 我们需要清理掉一部分,而不是转移它们 if len(self.memories) > self.max_memories:
if len(low_importance_memories) > self.max_memories:
# 目标保留数量(降至上限的 90% # 目标保留数量(降至上限的 90%
target_keep_count = int(self.max_memories * 0.9) 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) low_importance_memories.sort(key=lambda x: x.created_at)
to_remove = low_importance_memories[:num_to_remove] to_remove = low_importance_memories[:num_to_remove]
@@ -664,7 +664,7 @@ class ShortTermMemoryManager:
remove_ids = {mem.id for mem in to_remove} remove_ids = {mem.id for mem in to_remove}
self.memories = [mem for mem in self.memories if mem.id not in remove_ids] self.memories = [mem for mem in self.memories if mem.id not in remove_ids]
for mem_id 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) self._similarity_cache.pop(mem_id, None)
logger.info( logger.info(
@@ -675,6 +675,16 @@ class ShortTermMemoryManager:
# 触发保存 # 触发保存
asyncio.create_task(self._save_to_disk()) 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 return candidates
async def clear_transferred_memories(self, memory_ids: list[str]) -> None: 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_name = "poke_user"
action_description = "可以让你戳其他用户,为互动增添一份小小的乐趣" action_description = "戳一戳其他用户。这是一个需要谨慎使用的互动方式,默认只戳一次。群聊中应当克制使用,私聊中可以适当主动"
activation_type = ActionActivationType.ALWAYS activation_type = ActionActivationType.ALWAYS
parallel_action = True parallel_action = True
@@ -148,17 +148,48 @@ class PokeAction(BaseAction):
action_parameters: ClassVar[dict] = { action_parameters: ClassVar[dict] = {
"user_name": "需要戳一戳的用户的名字 (可选)", "user_name": "需要戳一戳的用户的名字 (可选)",
"user_id": "需要戳一戳的用户的ID (可选,优先级更高)", "user_id": "需要戳一戳的用户的ID (可选,优先级更高)",
"times": "需要戳一戳的次数 (默认为 1)", "times": "需要戳一戳的次数 (默认为 1最多3次)",
} }
action_require: ClassVar[list] = ["当需要戳某个用户时使用", "当你想提醒特定用户时使用"] action_require: ClassVar[list] = [
"用户明确要求戳某人时必须使用",
"私聊场景:可以在适当的互动时机主动使用(如回应戳一戳、俏皮互动等)",
"群聊场景:应当非常克制,仅在用户明确要求或有充分理由时才使用",
]
llm_judge_prompt = """ llm_judge_prompt = """
判定是否需要使用戳一戳动作的条件: 判定是否需要使用戳一戳动作的条件:
1. **互动时机**: 这是一个有趣的互动方式,可以在想提醒某人,或者单纯想开个玩笑时使用。
2. **用户请求**: 当用户明确要求使用戳一戳时。
3. **上下文需求**: 当上下文明确需要你戳一个或多个人时。
4. **频率与情绪**: 如果最近已经戳过,或者感觉对方情绪不高,请避免使用,不要打扰到别人哦。
请根据上述规则,回答“是”或“否”。 **必须遵守的严格规则:**
1. **用户明确要求**: 当用户明确说"戳XX""戳一下XX"等直接指令时,必须使用。
2. **群聊场景(非常克制)**:
- 群聊中应当非常谨慎,避免在公共场合频繁打扰他人
- 仅在以下情况考虑使用:用户明确要求、需要紧急提醒某人、或有特别充分的互动理由
- 如果最近已经在群里戳过任何人,必须回答""
- 群聊中不要随意主动使用,除非有明确必要性
3. **私聊场景(可以主动)**:
- 私聊中可以更加主动和俏皮
- 在以下情况可以使用:回应对方的戳一戳、轻松愉快的互动氛围、想要增添趣味时
- 但如果最近已经戳过对方,应避免频繁使用
4. **频率限制(重要)**:
- 如果最近已经戳过同一个人,必须回答""
- 默认只戳一次,不要多次戳别人(除非用户明确要求多次)
- 注意对方的情绪反应,如果对方看起来不高兴或不想被打扰,必须回答""
5. **禁止情况**:
- 对方情绪低落、生气、不耐烦时,严禁使用
- 严肃的对话场景中,严禁使用
- 刚刚戳过的情况下,严禁再次使用
**判断逻辑**:
- 首先判断是群聊还是私聊
- 群聊:除非用户明确要求或有特殊必要性,否则回答""
- 私聊:可以在合适的互动氛围中主动使用,但要注意频率
- 检查是否最近已经戳过,如果是则回答""
- 评估对方情绪和对话氛围是否适合
请严格根据上述规则,仅回答""""
""" """
associated_types: ClassVar[list[str]] = ["text"] associated_types: ClassVar[list[str]] = ["text"]