Compare commits
7 Commits
cf500a47de
...
bbe16046c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
bbe16046c8
|
|||
|
8a4719e6ac
|
|||
|
|
dd0dd94e76 | ||
|
|
3207aa31b1 | ||
|
|
6de5cd9902 | ||
|
|
1ad9c932bb | ||
|
|
8f2a6606eb |
32
.gitea/workflows/build.yaml
Normal file
32
.gitea/workflows/build.yaml
Normal 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 }}
|
||||
149
.github/workflows/docker-image.yml
vendored
149
.github/workflows/docker-image.yml
vendored
@@ -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
|
||||
@@ -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日
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 路径。
|
||||
|
||||
🎉 现在你已经掌握了完整的内存分析工具!
|
||||
@@ -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}")
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user