1 Commits
main ... sit

Author SHA1 Message Date
e3c68b5133 Update config, core models, and web app; refresh Python 3.11 cache
This commit includes general updates to application configuration, core data models, and various web application components (knowledge base, error handling, static assets, and websocket server). It also refreshes the Python 3.11 bytecode cache, likely due to underlying code changes and environment alignment.
2025-12-12 13:39:43 +08:00
357 changed files with 32120 additions and 18067 deletions

View File

@@ -1,109 +0,0 @@
# Architecture / 系统架构
## 整体架构
```mermaid
graph TB
subgraph Clients["客户端"]
Browser["浏览器 Dashboard"]
FeishuBot["飞书机器人"]
WSClient["WebSocket 客户端"]
end
subgraph EntryPoints["入口层"]
Flask["Flask App :5000"]
WS["WebSocket Server :8765"]
FeishuLC["飞书长连接服务"]
end
subgraph WebLayer["Web 层"]
Blueprints["16 个 Flask Blueprints"]
Decorators["装饰器: handle_errors, require_json, resolve_tenant_id, rate_limit"]
SM["ServiceManager (懒加载)"]
end
subgraph BusinessLayer["业务层"]
DM["DialogueManager"]
RCM["RealtimeChatManager"]
KM["KnowledgeManager"]
WOS["WorkOrderSyncService"]
Agent["ReactAgent"]
AM["AnalyticsManager"]
AS["AlertSystem"]
end
subgraph CoreLayer["基础设施层"]
DB["DatabaseManager (SQLAlchemy)"]
LLM["LLMClient (Qwen API)"]
Cache["CacheManager (Redis)"]
Auth["AuthManager (JWT)"]
Embed["EmbeddingClient (可选)"]
end
subgraph External["外部服务"]
QwenAPI["Qwen/DashScope API"]
FeishuAPI["飞书 API"]
RedisServer["Redis"]
Database["MySQL / SQLite"]
end
Browser --> Flask
WSClient --> WS
FeishuBot --> FeishuLC
Flask --> Blueprints
Blueprints --> Decorators
Blueprints --> SM
SM --> BusinessLayer
WS --> RCM
FeishuLC --> DM
DM --> LLM
DM --> KM
RCM --> DM
Agent --> LLM
KM --> Embed
WOS --> FeishuAPI
DB --> Database
LLM --> QwenAPI
Cache --> RedisServer
```
## 架构模式
### Singleton Managers
核心服务均为单例模式:`DatabaseManager`, `ServiceManager`, `UnifiedConfig`。通过 `get_config()` / `db_manager` 全局访问。
### Blueprint-per-Domain
每个功能域一个 Flask Blueprint共 16 个:
`workorders`, `alerts`, `knowledge`, `conversations`, `chat`, `agent`, `tenants`, `auth`, `analytics`, `monitoring`, `system`, `feishu_sync`, `feishu_bot`, `vehicle`, `core`, `test`
### Service Manager with Lazy Loading
`ServiceManager` 提供线程安全的懒初始化。Blueprint 通过它获取业务服务实例,避免循环导入和启动时的重量级初始化。
### Decorator-Driven API
通用横切关注点通过装饰器实现:
- `@handle_errors` — 统一异常处理
- `@require_json` — JSON 请求验证
- `@resolve_tenant_id` — 从请求中提取 tenant_id
- `@rate_limit` — 频率限制
- `@cache_response` — 响应缓存
### Multi-Tenant by Convention
所有核心表包含 `tenant_id` 字段,查询时按 tenant_id 过滤实现数据隔离。
## 线程模型
```mermaid
graph LR
Main["主线程: Flask App"]
T1["守护线程: WebSocket Server"]
T2["守护线程: 飞书长连接"]
Main --> T1
Main --> T2
```
`start_dashboard.py` 在主线程运行 FlaskWebSocket 和飞书长连接分别在守护线程中运行。

View File

@@ -1,51 +0,0 @@
# Codebase Info
## 基本信息
- **项目名称**: TSP 智能助手 (TSP Assistant)
- **语言**: Python 3.11+
- **框架**: Flask 3.x + SQLAlchemy 2.x + WebSocket
- **代码风格**: 变量名英文,注释/UI/日志中文
- **数据库**: SQLAlchemy ORM开发用 SQLite生产用 MySQL via PyMySQL
- **入口文件**: `start_dashboard.py`
## 技术栈
| 层级 | 技术 |
|---|---|
| Web | Flask 3.x + Flask-CORS |
| ORM | SQLAlchemy 2.x |
| 实时通信 | `websockets` (port 8765) |
| 缓存 | Redis 5.x + hiredis |
| LLM | OpenAI-compatible API (Qwen/通义千问 via DashScope) |
| Embedding | `sentence-transformers` + `BAAI/bge-small-zh-v1.5` (可选) |
| NLP | jieba (分词) + scikit-learn (TF-IDF) |
| 飞书 SDK | `lark-oapi` 1.3.x (长连接模式) |
| 认证 | JWT (`pyjwt`) + SHA-256 |
| 监控 | psutil |
## 目录结构概览
```
src/
├── config/ # UnifiedConfig 单例,从 .env 加载
├── core/ # 数据库、LLM、缓存、认证、ORM 模型
├── dialogue/ # 对话管理、实时聊天
├── knowledge_base/ # 知识库 CRUD、搜索、导入
├── analytics/ # 监控、预警、Token 统计
├── integrations/ # 飞书客户端、工单同步
├── agent/ # ReAct Agent工具调度
├── vehicle/ # 车辆数据管理
├── utils/ # 通用工具
└── web/ # Flask 应用层
├── app.py # 应用工厂 + 中间件
├── service_manager.py # 懒加载服务注册
├── decorators.py # 通用装饰器
├── blueprints/ # 按领域划分的 API 蓝图 (16 个)
├── static/ # 前端资源
└── templates/ # Jinja2 模板
```
## 启动流程
`start_dashboard.py` → 设置日志 → 检查数据库 → 启动 WebSocket 线程 → 启动飞书长连接线程 → 启动 Flask 应用

View File

@@ -1,79 +0,0 @@
# Components / 核心组件
## 业务组件
### DialogueManager (`src/dialogue/dialogue_manager.py`)
对话管理核心。处理用户消息,调用 LLM 生成回复,根据意图自动创建工单。连接知识库检索和 LLM 调用。
### RealtimeChatManager (`src/dialogue/realtime_chat.py`)
实时聊天管理器。管理 WebSocket 会话,维护 `ChatMessage` 数据结构,协调 DialogueManager 处理消息流。
### KnowledgeManager (`src/knowledge_base/knowledge_manager.py`)
知识库管理。支持 CRUD、TF-IDF 关键词搜索、可选 Embedding 语义搜索、文件导入、人工验证。已验证条目在检索时获得更高置信度。
### WorkOrderSyncService (`src/integrations/workorder_sync.py`)
工单同步服务。实现本地工单与飞书多维表格的双向同步。定义 `WorkOrderStatus``WorkOrderPriority` 枚举。
### ReactAgent (`src/agent/react_agent.py`)
ReAct 风格 LLM Agent。注册工具知识搜索、车辆查询、分析、飞书消息通过思考-行动-观察循环完成复杂任务。
### AnalyticsManager (`src/analytics/analytics_manager.py`)
数据分析管理器。工单趋势、预警统计、满意度分析,支持按租户筛选。
### AlertSystem (`src/analytics/alert_system.py`)
预警系统。自定义 `AlertRule`,支持多级别(`AlertLevel`)和多类型(`AlertType`)预警,批量管理。
## 基础设施组件
### DatabaseManager (`src/core/database.py`)
数据库管理单例。封装 SQLAlchemy session 管理,提供 `get_session()` 上下文管理器。支持 SQLite开发和 MySQL生产
### LLMClient (`src/core/llm_client.py`)
LLM 调用客户端。封装 OpenAI-compatible API 调用(默认 Qwen/DashScope支持流式和非流式响应。
### UnifiedConfig (`src/config/unified_config.py`)
配置单例。从 `.env` 加载环境变量,映射到 typed dataclasses
`DatabaseConfig`, `LLMConfig`, `ServerConfig`, `FeishuConfig`, `AIAccuracyConfig`, `EmbeddingConfig`, `RedisConfig`
### AuthManager (`src/core/auth_manager.py`)
认证管理。JWT token 生成/验证SHA-256 密码哈希。
### ServiceManager (`src/web/service_manager.py`)
服务注册中心。线程安全的懒加载Blueprint 通过它获取业务服务实例。
## 集成组件
### FeishuService (`src/integrations/feishu_service.py`)
飞书 API 客户端。获取 tenant_access_token发送消息操作多维表格。
### FeishuLongConnService (`src/integrations/feishu_longconn_service.py`)
飞书事件订阅长连接服务。接收飞书机器人消息事件,转发给 DialogueManager 处理。
## Web 层组件
### Flask Blueprints (`src/web/blueprints/`)
```mermaid
graph TD
subgraph API["API Blueprints"]
WO["workorders — 工单 CRUD + AI 建议"]
AL["alerts — 预警管理"]
KN["knowledge — 知识库管理"]
CV["conversations — 对话历史"]
CH["chat — HTTP 聊天 + 会话管理"]
AG["agent — Agent 模式交互"]
TN["tenants — 租户管理"]
AU["auth — 登录/注册/JWT"]
AN["analytics — 数据分析导出"]
MO["monitoring — Token/AI 监控"]
SY["system — 系统配置/备份/优化"]
FS["feishu_sync — 飞书同步配置"]
FB["feishu_bot — 飞书 Webhook 事件"]
VH["vehicle — 车辆数据"]
CO["core — 监控规则/批量操作"]
TE["test — API 连接测试"]
end
```
### WebSocketServer (`src/web/websocket_server.py`)
独立 WebSocket 服务器port 8765。处理客户端连接转发消息给 RealtimeChatManager。

View File

@@ -1,136 +0,0 @@
# Data Models / 数据模型
## ORM 模型 (`src/core/models.py`)
所有模型继承 SQLAlchemy `Base`,核心表均包含 `tenant_id` 字段用于多租户隔离。
```mermaid
erDiagram
Tenant ||--o{ WorkOrder : "tenant_id"
Tenant ||--o{ ChatSession : "tenant_id"
Tenant ||--o{ KnowledgeEntry : "tenant_id"
Tenant ||--o{ Alert : "tenant_id"
Tenant ||--o{ Analytics : "tenant_id"
WorkOrder ||--o{ Conversation : "work_order_id"
WorkOrder ||--o{ WorkOrderProcessHistory : "work_order_id"
WorkOrder ||--o{ WorkOrderSuggestion : "work_order_id"
ChatSession ||--o{ Conversation : "session_id"
Tenant {
int id PK
string tenant_id UK
string name
text description
bool is_active
text config "JSON: feishu, system_prompt"
}
WorkOrder {
int id PK
string tenant_id FK
string order_id UK
string title
text description
string category
string priority
string status
string feishu_record_id "飞书记录ID"
string assignee
text ai_suggestion
string assigned_module
string module_owner
string vin_sim "车架号"
}
ChatSession {
int id PK
string tenant_id FK
string session_id UK
string source "websocket/api/feishu_bot"
}
Conversation {
int id PK
string tenant_id FK
int work_order_id FK
string session_id FK
string role "user/assistant/system"
text content
}
KnowledgeEntry {
int id PK
string tenant_id FK
string question
text answer
string category
bool is_verified
float confidence_score
}
Alert {
int id PK
string tenant_id FK
string level
string type
text message
bool is_resolved
}
VehicleData {
int id PK
string vehicle_id
string vin
text data "JSON"
}
Analytics {
int id PK
string tenant_id FK
string metric_type
float value
text details "JSON"
}
User {
int id PK
string username UK
string password_hash
string role "admin/user"
}
WorkOrderProcessHistory {
int id PK
int work_order_id FK
string action
text details
}
WorkOrderSuggestion {
int id PK
int work_order_id FK
text suggestion
float confidence
}
```
## 配置 Dataclasses (`src/config/unified_config.py`)
| Dataclass | 关键字段 |
|-----------|---------|
| `DatabaseConfig` | `url` |
| `LLMConfig` | `api_key`, `base_url`, `model`, `temperature`, `max_tokens`, `timeout` |
| `ServerConfig` | `host`, `port`, `websocket_port`, `debug`, `log_level` |
| `FeishuConfig` | `app_id`, `app_secret`, `app_token`, `table_id` |
| `AIAccuracyConfig` | `auto_approve_threshold`, `manual_review_threshold` |
| `EmbeddingConfig` | `enabled`, `model`, `dimension`, `similarity_threshold` |
| `RedisConfig` | `host`, `port`, `db`, `password`, `pool_size`, `default_ttl`, `enabled` |
## 业务枚举
- `WorkOrderStatus`: 工单状态流转
- `WorkOrderPriority`: 工单优先级
- `AlertLevel`: 预警级别
- `AlertType`: 预警类型

View File

@@ -1,90 +0,0 @@
# Dependencies / 外部依赖
## 核心依赖
| 包 | 版本 | 用途 |
|---|---|---|
| flask | 3.0.3 | Web 框架 |
| flask-cors | 5.0.0 | 跨域支持 |
| sqlalchemy | 2.0.32 | ORM |
| pymysql | 1.1.1 | MySQL 驱动 |
| websockets | 15.0.1 | WebSocket 服务器 |
| redis | 5.0.1 | 缓存客户端 |
| hiredis | 2.3.2 | Redis 高性能解析器 |
## LLM / NLP
| 包 | 版本 | 用途 |
|---|---|---|
| jieba | 0.42.1 | 中文分词 |
| scikit-learn | 1.4.2 | TF-IDF 向量化 |
| numpy | 1.26.4 | 数值计算 |
## 飞书集成
| 包 | 版本 | 用途 |
|---|---|---|
| lark-oapi | 1.3.5 | 飞书 SDK事件订阅 2.0,长连接模式) |
## 数据处理
| 包 | 版本 | 用途 |
|---|---|---|
| pandas | 2.2.2 | 数据分析 |
| openpyxl | 3.1.5 | Excel 读写 |
| ujson | 5.10.0 | 高性能 JSON |
## 安全 / 认证
| 包 | 版本 | 用途 |
|---|---|---|
| pyjwt | 2.9.0 | JWT token |
| bcrypt | 4.2.1 | 密码哈希 |
| cryptography | 43.0.1 | 加密支持 |
## 数据验证
| 包 | 版本 | 用途 |
|---|---|---|
| pydantic | 2.9.2 | 数据验证 |
| marshmallow | 3.23.3 | 序列化/反序列化 |
## 监控 / 工具
| 包 | 版本 | 用途 |
|---|---|---|
| psutil | 5.9.8 | 系统监控 |
| python-dotenv | 1.0.1 | 环境变量加载 |
| structlog | 24.4.0 | 结构化日志 |
| aiohttp | 3.10.10 | 异步 HTTP |
| httpx | 0.27.2 | HTTP 客户端 |
## 可选依赖
| 包 | 用途 | 条件 |
|---|---|---|
| sentence-transformers | 本地 Embedding 模型 | `EMBEDDING_ENABLED=True` |
| torch | PyTorchsentence-transformers 依赖) | `EMBEDDING_ENABLED=True` |
## 开发依赖
| 包 | 版本 | 用途 |
|---|---|---|
| pytest | 8.3.3 | 测试框架 |
| pytest-asyncio | 0.24.0 | 异步测试 |
| pytest-cov | 6.0.0 | 覆盖率 |
| black | 24.8.0 | 代码格式化 |
| flake8 | 7.1.1 | Linting |
| mypy | 1.11.1 | 类型检查 |
| isort | 5.13.2 | Import 排序 |
## 外部服务依赖
```mermaid
graph LR
App["TSP Assistant"] --> Qwen["Qwen/DashScope API"]
App --> FeishuAPI["飞书 Open API"]
App --> Redis["Redis Server"]
App --> DB["MySQL / SQLite"]
App --> HF["HuggingFace (可选, 首次下载模型)"]
```

View File

@@ -1,56 +0,0 @@
# TSP 智能助手 — 文档索引
> **面向 AI 助手的使用指南**: 本文件是文档体系的入口。根据问题类型查阅对应文件即可获取详细信息。大多数问题只需本文件 + 1~2 个子文件即可回答。
## 文档目录
| 文件 | 内容摘要 | 适用问题 |
|------|---------|---------|
| [codebase_info.md](codebase_info.md) | 项目基本信息、技术栈、目录结构、启动流程 | "这个项目是什么?" "用了什么技术?" "怎么启动?" |
| [architecture.md](architecture.md) | 系统架构图、架构模式(单例/Blueprint/装饰器/多租户)、线程模型 | "系统怎么组织的?" "请求怎么流转?" "多租户怎么实现?" |
| [components.md](components.md) | 所有核心组件的职责说明业务层、基础设施层、Web 层) | "DialogueManager 做什么?" "有哪些 Blueprint" |
| [interfaces.md](interfaces.md) | REST API 列表、WebSocket 接口、外部集成时序图、装饰器接口 | "工单 API 有哪些?" "飞书怎么集成的?" |
| [data_models.md](data_models.md) | ORM 模型 ER 图、字段说明、配置 Dataclass、业务枚举 | "WorkOrder 有哪些字段?" "数据库表结构?" |
| [workflows.md](workflows.md) | 6 个关键流程的时序图启动、对话、工单同步、知识搜索、飞书消息、Agent | "消息怎么处理的?" "工单怎么同步到飞书?" |
| [dependencies.md](dependencies.md) | 所有 Python 依赖分类说明、外部服务依赖图 | "用了哪些库?" "有哪些外部依赖?" |
| [review_notes.md](review_notes.md) | 文档一致性/完整性检查结果、待补充区域、改进建议 | "文档有什么遗漏?" "哪些地方需要补充?" |
## 快速导航
### 按任务类型
- **修 Bug / 改功能** → `components.md` 找到对应组件 → `architecture.md` 理解依赖关系
- **加新 API** → `interfaces.md` 了解现有 API 模式 → `architecture.md` 中的装饰器模式
- **改数据库** → `data_models.md` 查看表结构和关系
- **理解流程** → `workflows.md` 查看时序图
- **加新依赖** → `dependencies.md` 了解现有依赖
- **部署/配置** → `codebase_info.md` 查看启动方式
### 按代码位置
- `src/core/``components.md` 基础设施组件 + `data_models.md`
- `src/web/blueprints/``interfaces.md` API 列表 + `components.md` Blueprint 说明
- `src/dialogue/``components.md` 对话组件 + `workflows.md` 对话流程
- `src/integrations/``interfaces.md` 外部集成 + `workflows.md` 飞书流程
- `src/config/``data_models.md` 配置 Dataclass
- `start_dashboard.py``workflows.md` 启动流程
## 文件间关系
```mermaid
graph TD
INDEX["index.md (本文件)"] --> CI["codebase_info.md"]
INDEX --> ARCH["architecture.md"]
INDEX --> COMP["components.md"]
INDEX --> INTF["interfaces.md"]
INDEX --> DM["data_models.md"]
INDEX --> WF["workflows.md"]
INDEX --> DEP["dependencies.md"]
INDEX --> RN["review_notes.md"]
ARCH --> COMP
COMP --> INTF
COMP --> DM
INTF --> WF
DM --> WF
```

View File

@@ -1,104 +0,0 @@
# Interfaces / 接口与集成
## REST API 概览
所有 API 以 `/api/` 为前缀,返回 JSON。认证通过 Flask session 或 JWT。
### 工单 (workorders)
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/workorders` | 工单列表(分页) |
| POST | `/api/workorders` | 创建工单 |
| GET | `/api/workorders/<id>` | 工单详情 |
| PUT | `/api/workorders/<id>` | 更新工单 |
| DELETE | `/api/workorders/<id>` | 删除工单 |
| POST | `/api/workorders/ai-suggestion` | 生成 AI 建议 |
| POST | `/api/workorders/import` | 批量导入 |
### 知识库 (knowledge)
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/knowledge` | 知识条目列表 |
| POST | `/api/knowledge` | 添加条目 |
| GET | `/api/knowledge/search` | 搜索知识库 |
| GET | `/api/knowledge/stats` | 统计信息 |
| POST | `/api/knowledge/upload` | 文件导入 |
| PUT | `/api/knowledge/<id>/verify` | 验证条目 |
### 对话 (chat / conversations)
| Method | Path | 说明 |
|--------|------|------|
| POST | `/api/chat/sessions` | 创建会话 |
| GET | `/api/chat/sessions` | 活跃会话列表 |
| POST | `/api/chat/message` | 发送消息 |
| POST | `/api/chat/message/stream` | 流式消息 (SSE) |
| GET | `/api/conversations` | 对话历史 |
### 租户 (tenants)
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/tenants` | 租户列表 |
| POST | `/api/tenants` | 创建租户 |
| PUT | `/api/tenants/<id>` | 更新租户 |
| DELETE | `/api/tenants/<id>` | 删除租户 |
| GET | `/api/tenants/feishu-groups` | 飞书群列表 |
### 认证 (auth)
| Method | Path | 说明 |
|--------|------|------|
| POST | `/api/auth/login` | 登录 |
| POST | `/api/auth/logout` | 登出 |
| GET | `/api/auth/status` | 认证状态 |
| POST | `/api/auth/register` | 注册 |
### Agent
| Method | Path | 说明 |
|--------|------|------|
| POST | `/api/agent/chat` | Agent 对话 |
| GET | `/api/agent/status` | Agent 状态 |
| POST | `/api/agent/tools/execute` | 执行工具 |
### 飞书同步 (feishu-sync)
| Method | Path | 说明 |
|--------|------|------|
| GET | `/api/feishu-sync/status` | 同步状态 |
| POST | `/api/feishu-sync/from-feishu` | 从飞书拉取 |
| POST | `/api/feishu-sync/<id>/to-feishu` | 推送到飞书 |
| GET/POST | `/api/feishu-sync/config` | 同步配置 |
## WebSocket 接口
- **端口**: 8765
- **协议**: JSON 消息
- **功能**: 实时聊天,客户端连接后通过 JSON 消息与 RealtimeChatManager 交互
## 外部集成
```mermaid
sequenceDiagram
participant User as 用户
participant Feishu as 飞书
participant LongConn as 飞书长连接服务
participant DM as DialogueManager
participant LLM as Qwen API
participant KB as KnowledgeManager
User->>Feishu: 发送消息
Feishu->>LongConn: 事件推送
LongConn->>DM: 处理消息
DM->>KB: 知识库检索
KB-->>DM: 相关知识
DM->>LLM: 生成回复
LLM-->>DM: AI 回复
DM->>Feishu: 回复消息
```
## 装饰器接口
| 装饰器 | 位置 | 功能 |
|--------|------|------|
| `@handle_errors` | `decorators.py` | 统一异常捕获,返回标准错误响应 |
| `@require_json(fields)` | `decorators.py` | 验证请求体为 JSON 且包含必填字段 |
| `@with_service(name)` | `decorators.py` | 从 ServiceManager 注入服务实例 |
| `@rate_limit(max, period)` | `decorators.py` | 基于 IP 的频率限制 |
| `@cache_response(timeout)` | `decorators.py` | 响应缓存 |

View File

@@ -1,41 +0,0 @@
# Review Notes / 审查记录
## 一致性检查 ✅
- 所有文档中的组件名称、文件路径与代码库一致
- 数据模型字段与 `src/core/models.py` 中的 ORM 定义匹配
- API 路径与 Blueprint 注册的路由一致
- 配置项与 `.env.example` 中的变量对应
## 完整性检查
### 已充分覆盖的区域 ✅
- 系统架构和线程模型
- 核心业务组件对话、工单、知识库、Agent
- 数据模型和 ER 关系
- REST API 接口概览
- 外部集成飞书、LLM
- 启动流程和关键工作流
### 需要补充的区域 ⚠️
| 区域 | 说明 | 建议 |
|------|------|------|
| `src/core/cache_manager.py` | 缓存策略细节未深入分析 | 补充 Redis 缓存键命名规范和 TTL 策略 |
| `src/core/vector_store.py` | 向量存储实现细节 | 补充 Embedding 索引结构说明 |
| `src/core/embedding_client.py` | Embedding 客户端接口 | 补充模型加载和推理流程 |
| `src/web/static/js/` | 前端模块化结构 | 补充前端模块职责说明 |
| `src/repositories/` | 数据访问层steering 中提到但未深入) | 补充 Repository 模式和自动 tenant_id 过滤机制 |
| 错误处理 | `error_handlers.py` 的统一响应格式 | 补充 API 错误码规范 |
| 部署配置 | Docker / Nginx 配置细节 | 补充 `nginx.conf` 和 docker-compose 说明 |
### 语言支持限制
- 代码注释和 UI 文本为中文,文档已采用中英混合风格覆盖
- 无其他语言支持限制
## 建议
1.`src/repositories/` 层补充文档,说明自动 tenant_id 过滤的实现
2. 补充前端 JS 模块的职责划分文档
3. 考虑为 API 添加 OpenAPI/Swagger 规范
4. 补充部署相关的 Docker 和 Nginx 配置说明

View File

@@ -1,127 +0,0 @@
# Workflows / 关键流程
## 1. 应用启动流程
```mermaid
sequenceDiagram
participant Main as start_dashboard.py
participant Log as setup_logging
participant DB as DatabaseManager
participant WS as WebSocket Thread
participant FS as 飞书长连接 Thread
participant Flask as Flask App
Main->>Log: 初始化日志(按启动时间分目录)
Main->>DB: check_database_connection()
alt 连接失败
Main->>Main: sys.exit(1)
end
Main->>WS: 启动守护线程 (port 8765)
Main->>FS: 启动守护线程 (飞书长连接)
Main->>Main: sleep(2) 等待初始化
Main->>Flask: app.run(port=5000, threaded=True)
```
## 2. 用户对话流程 (WebSocket)
```mermaid
sequenceDiagram
participant Client as 浏览器
participant WS as WebSocketServer
participant RCM as RealtimeChatManager
participant DM as DialogueManager
participant KB as KnowledgeManager
participant LLM as LLMClient
Client->>WS: WebSocket 连接
Client->>WS: JSON 消息
WS->>RCM: 转发消息
RCM->>DM: process_message()
DM->>KB: search() 知识库检索
KB-->>DM: 匹配结果
DM->>LLM: 调用 Qwen API含知识上下文
LLM-->>DM: AI 回复
DM-->>RCM: 回复内容
RCM-->>WS: 发送回复
WS-->>Client: JSON 回复
```
## 3. 工单创建与飞书同步
```mermaid
sequenceDiagram
participant User as 用户/AI
participant API as workorders Blueprint
participant DB as Database
participant Sync as WorkOrderSyncService
participant Feishu as 飞书多维表格
User->>API: POST /api/workorders
API->>DB: 创建工单记录
DB-->>API: 工单 ID
opt 飞书同步已配置
API->>Sync: sync_to_feishu(workorder_id)
Sync->>Feishu: 创建/更新记录
Feishu-->>Sync: feishu_record_id
Sync->>DB: 更新 feishu_record_id
end
API-->>User: 工单创建成功
```
## 4. 知识库搜索流程
```mermaid
flowchart TD
Q["用户查询"] --> TF["TF-IDF 关键词匹配"]
Q --> EMB{"Embedding 启用?"}
EMB -->|是| VEC["向量语义搜索"]
EMB -->|否| SKIP["跳过"]
TF --> MERGE["合并结果"]
VEC --> MERGE
SKIP --> MERGE
MERGE --> RANK["按相似度排序"]
RANK --> VERIFIED{"已验证条目优先"}
VERIFIED --> RESULT["返回 Top-K 结果"]
```
## 5. 飞书机器人消息处理
```mermaid
sequenceDiagram
participant User as 飞书用户
participant Feishu as 飞书服务器
participant LC as FeishuLongConnService
participant DM as DialogueManager
participant LLM as LLMClient
participant FS as FeishuService
User->>Feishu: @机器人 发送消息
Feishu->>LC: 长连接事件推送
LC->>LC: 消息去重
LC->>LC: resolve_tenant_by_chat_id()
LC->>DM: 处理消息(带 tenant_id
DM->>LLM: 生成回复
LLM-->>DM: AI 回复
DM->>FS: 发送飞书消息
FS->>Feishu: API 回复
```
## 6. Agent 工具调度
```mermaid
flowchart TD
INPUT["用户输入"] --> THINK["Thought: 分析意图"]
THINK --> ACT["Action: 选择工具"]
ACT --> EXEC["执行工具"]
EXEC --> OBS["Observation: 获取结果"]
OBS --> DONE{"任务完成?"}
DONE -->|否| THINK
DONE -->|是| ANSWER["Final Answer: 返回结果"]
```
ReactAgent 注册的工具包括:知识库搜索、车辆查询、数据分析、飞书消息发送等。

View File

@@ -1,34 +0,0 @@
---
name: doc-updater
description: "Use this agent when code has been modified and you need to update the corresponding Markdown documentation to reflect those changes. This is crucial after refactoring a function, changing an API endpoint, adding a new feature, or altering configuration that is documented in `.md` files.\\n<example>\\nContext: The user has just changed a function signature in their Python code.\\nuser: \"I've just refactored the `process_data` function. It now takes a dictionary instead of two separate lists.\"\\nassistant: \"I see that the function signature for `process_data` has changed. This likely impacts the documentation. I will use the Task tool to launch the `doc-updater` agent to find and update any relevant code examples and descriptions in the Markdown files.\"\\n<commentary>\\nSince a core function's signature was modified, the agent correctly identifies the need to update documentation and calls the `doc-updater` to handle the task.\\n</commentary>\\n</example>\\n<example>\\nContext: A user has added a new environment variable to their application's configuration.\\nuser: \"I added `API_TIMEOUT` to the config. It defaults to 30 seconds.\"\\nassistant: \"Okay, a new configuration variable has been added. To ensure our project documentation is current, I'll use the `doc-updater` agent to add details about `API_TIMEOUT` to the `CONFIGURATION.md` file.\"\\n<commentary>\\nThe user modified project configuration, which is a common source of documentation drift. The agent proactively uses the `doc-updater` to keep the documentation synchronized.\\n</commentary>\\n</example>"
model: inherit
---
You are an expert Documentation Synchronizer, a meticulous agent dedicated to keeping project documentation perfectly aligned with its source code. Your primary responsibility is to automatically update Markdown (.md, .mdx) files whenever there are corresponding code changes.
Your operational workflow is as follows:
1. **Analyze the Code Change**: Upon activation, your first step is to thoroughly understand the provided code modifications. Identify the core nature of the change:
* Has a function's signature (parameters, return type) been altered?
* Has a new class, method, or function been added? Or an old one removed?
* Has an API endpoint's request/response structure changed?
* Have configuration details or environment variables been updated?
2. **Locate Relevant Documentation**: Systematically search the project for all Markdown files that reference the modified code. This includes API guides, READMEs, tutorials, and architectural documents.
3. **Assess the Impact and Update**: For each relevant document, determine the precise impact of the code change and perform the necessary edits.
* **Update Code Snippets**: Ensure all code examples accurately reflect the new implementation.
* **Adjust Textual Descriptions**: Modify parameter descriptions, explanations of functionality, and return value details.
* **Preserve Style and Tone**: Maintain the existing writing style, formatting, and voice of the document you are editing. Do not introduce new conventions.
* **Add New Content**: If a new feature is introduced, create a new documentation section for it, meticulously following the structure of existing sections.
4. **Handle Non-Impactful Changes**: If you determine that a code change (e.g., an internal refactor, performance tweak) has no impact on the existing documentation, you must explicitly report this. State clearly that no documentation update is necessary and briefly explain why.
5. **Address Missing Documentation**: If you find that a modified piece of code is not documented at all, you should flag this as a documentation gap. Propose where and how it could be documented.
6. **Verify and Finalize**: Before concluding, conduct a final self-review.
* Does the updated documentation accurately reflect the code?
* Are the code snippets syntactically correct?
* Is the formatting clean and consistent with the rest of the file?
Your output should be the complete, updated content of the modified Markdown file(s). If multiple files are changed, clearly delineate each one. Provide a concise summary of the changes you made for user verification.

View File

@@ -1,31 +0,0 @@
---
name: frontend-sync
description: "Use this agent when back-end code (e.g., APIs, data models, business logic) has been modified, and the corresponding front-end functionality needs to be updated to reflect those changes.\\n<example>\\nContext: The user has just added a new field to a data model on the backend and updated the corresponding API endpoint.\\nuser: \"I've added an 'author' field to the BlogPost model and updated the `/api/posts/:id` endpoint to include it in the response.\"\\nassistant: \"The backend is updated. I will now use the Task tool to launch the `frontend-sync` agent to update the post display component to show the new author field.\"\\n<commentary>\\nSince the backend data structure and API response have changed, the frontend code that consumes this data must be updated. This is the primary trigger for the `frontend-sync` agent.\\n</commentary>\\n</example>\\n<example>\\nContext: The user has changed the method of an API endpoint from GET to POST for security reasons.\\nuser: \"I've changed the `/api/user/settings` endpoint from a GET to a POST request to prevent sensitive data from appearing in URLs.\"\\nassistant: \"Understood. The API contract has changed. I will now use the Task tool to launch the `frontend-sync` agent to refactor the settings page to use a POST request to fetch user settings.\"\\n<commentary>\\nA change in the API's method requires a corresponding change in the frontend's data-fetching logic. The `frontend-sync` agent is designed for this exact task.\\n</commentary>\\n</example>"
model: inherit
---
You are a Senior Full-Stack Synchronizer, an expert agent specializing in keeping front-end applications perfectly in sync with back-end code modifications. Your mission is to analyze recent back-end code changes, identify the impacted front-end components, and apply the necessary updates to ensure seamless integration and functionality.
### Your Workflow
1. **Analyze Back-End Changes**: Begin by meticulously reviewing the description of the back-end code modifications. Understand the core nature of the change: Is it a new API endpoint, a change in a data model, a modification to business logic, or a bug fix?
2. **Identify Front-End Impact**: Based on your analysis, use your knowledge of the project structure to locate all relevant front-end files (e.g., React/Vue components, Svelte files, HTML templates, data-fetching services, state management stores) that are affected by the back-end changes. This includes files that consume the modified API, display the altered data, or depend on the updated logic.
3. **Formulate an Update Plan**: Before writing any code, formulate a clear and concise plan. Your plan should detail:
* Which files you will modify.
* How you will adjust API calls (e.g., change URL, method, headers, request body, or response handling).
* How you will update front-end data structures or types (e.g., TypeScript interfaces) to match new models.
* Which UI components need to be adjusted to display new data or handle new states.
* Any new components or views that need to be created.
4. **Execute the Update**: Implement the planned changes to the identified front-end files. Write clean, maintainable, and high-quality code that strictly adheres to the project's existing coding standards, style guides, and architectural patterns. Ensure your changes are focused and directly address the requirements of the back-end modification.
5. **Verify and Summarize**: After applying the changes, briefly describe how the new front-end code works and confirm that it correctly aligns with the back-end updates. Summarize your work, listing the files you modified and the key changes you made.
### Guiding Principles
* **Precision is Key**: Your changes must be precise. Only modify the code necessary to align with the back-end changes. Avoid unrelated refactoring.
* **Maintain Consistency**: Your code must seamlessly integrate with the existing front-end architecture, state management (e.g., Redux, Vuex, Pinia), and UI component libraries.
* **Seek Clarity**: If the impact of a back-end change is ambiguous or unclear, you MUST ask for clarification before proceeding with any modifications. State what information you need to move forward.
* **Prioritize User Experience**: Ensure that your updates do not degrade the user experience. The UI should remain responsive, intuitive, and visually consistent.

View File

@@ -1,21 +1,7 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(git config:*)",
"Bash(python:*)",
"Bash(python3:*)",
"Bash(curl:*)",
"Bash(pip show:*)",
"Bash(find:*)",
"Bash(chmod:*)",
"Bash(open:*)",
"Bash(ls:*)",
"Bash(.gitignore:*)",
"Bash(git reset:*)",
"Bash(lsof:*)"
"Bash(curl:*)"
],
"deny": [],
"ask": []

View File

@@ -1,92 +0,0 @@
---
Name: ai-metrics-report
Description: 基于现有监控与分析模块,生成一份最近一段时间的 AI 成功率、错误率与 Token 成本的综合报告,用于评估 TSP 智能助手的整体表现。
---
你是一个「AI 指标报告助手」,技能名为 **ai-metrics-report**
你的职责:当用户希望了解一段时间内 AI 助手的表现成功率、错误率、响应时间、Token 成本等)时,自动调用配套脚本,基于现有监控与分析模块,生成一份面向运营/技术同学都能看懂的综合报告。
---
## 一、触发条件(什么时候使用 ai-metrics-report
当用户有类似需求时,应激活本 Skill例如
- 「帮我出一份最近 7 天 AI 调用表现的报告」
- 「看一下最近 AI 的成功率和错误率」
- 「近一周 Token 消耗和成本情况如何」
- 「AI 现在效果怎么样,有没有明显波动」
---
## 二、总体流程
1. 从项目根目录执行脚本 `scripts/ai_metrics_report.py`
2. 读取脚本输出的结构化指标数据JSON 或结构化文本);
3. 将关键指标用自然语言转述,并做简要分析(例如是否达标、是否有波动);
4. 如发现明显异常(高错误率 / 成本突增 / 成功率显著下降),给出 13 条排查或优化建议。
---
## 三、脚本调用规范
从项目根目录执行命令(可传入天数参数,默认 7 天):
```bash
python .claude/skills/ai-metrics-report/scripts/ai_metrics_report.py --days 7
```
脚本行为约定:
- 尝试复用现有模块:
- `src.analytics.ai_success_monitor.AISuccessMonitor`(如提供聚合接口则优先使用);
- `src.analytics.token_monitor.TokenMonitor.get_system_token_stats(days=...)`
- 至少输出以下信息(以 JSON 或清晰的文本格式打印到 stdout
- 时间范围(例如「最近 7 天」)
- 会话类指标:
- 总调用次数、成功调用次数、失败调用次数
- 成功率、平均响应时间
- Token/成本指标:
- 总 Token 数
- 总成本
- 按模型维度的请求数占比(如 `qwen-plus-latest` 等)
- 简单趋势:
- 按天的调用次数与成本(可选)
你需要:
1. 运行脚本并捕获输出;
2. 解析其中关键字段;
3. 用 510 句中文自然语言,对用户生成一份「运营视角」的口头报告。
---
## 四、对用户的输出规范
当成功执行 `ai-metrics-report` 时,应返回如下结构的信息:
1. **时间范围与总体结论**
- 例如:「最近 7 天AI 总调用 1234 次,成功率约 96%,整体表现稳定。」
2. **关键指标分项**
- 成功率 / 错误率、平均响应时间;
- 总 Token 使用量与总成本;
- 主力模型(如 `qwen-plus-latest`)的占比;
3. **趋势与风险提醒**
- 若发现某几天错误率或成本异常升高,应指出并提醒。
4. **建议(可选)**
- 如「可以考虑优化 prompt 降低平均 Token」「错误集中在某业务接口建议重点排查」等。
避免:
- 直接把原始 JSON 或 Python repr 整段贴给用户;
- 输出过多技术细节,优先用业务/运营语言阐述。
---
## 五、反模式与边界
- 如脚本运行失败应告诉用户「ai-metrics-report 脚本运行失败」,简要给出错误原因;
- 不要直接访问数据库执行复杂 SQL优先复用已有封装好的监控/统计接口;
- 不要修改任何生产配置或监控阈值,仅进行只读分析和报告。

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
AI 指标报告脚本
聚合最近一段时间的 AI 使用与成本指标,供 ai-metrics-report Skill 调用。
当前版本主要复用 TokenMonitor 的系统统计能力。
"""
import argparse
import sys
from pathlib import Path
def add_project_root_to_path():
# 假定脚本位于 .claude/skills/ai-metrics-report/scripts/ 下
script_path = Path(__file__).resolve()
project_root = script_path.parents[4]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
def main():
parser = argparse.ArgumentParser(description="Generate AI metrics report.")
parser.add_argument(
"--days",
type=int,
default=7,
help="Number of days to include in the report (default: 7).",
)
args = parser.parse_args()
add_project_root_to_path()
from src.analytics.token_monitor import TokenMonitor
monitor = TokenMonitor()
stats = monitor.get_system_token_stats(days=args.days) or {}
print(f"=== AI 指标报告(最近 {args.days} 天) ===\n")
total_tokens = stats.get("total_tokens", 0)
total_cost = stats.get("total_cost", 0.0)
total_requests = stats.get("total_requests", 0)
successful_requests = stats.get("successful_requests", 0)
failed_requests = stats.get("failed_requests", 0)
success_rate = stats.get("success_rate", 0)
print("整体概览:")
print(f" 总请求数 : {total_requests}")
print(f" 成功请求数 : {successful_requests}")
print(f" 失败请求数 : {failed_requests}")
print(f" 成功率 : {success_rate:.2%}" if total_requests else " 成功率 : N/A")
print(f" 总 Token 数量 : {total_tokens}")
print(f" 总成本(估算) : {total_cost:.4f}")
print()
# 模型使用分布
model_usage = stats.get("model_usage", {})
if model_usage:
print("按模型维度的请求分布:")
for model_name, count in model_usage.items():
pct = (count / total_requests) * 100 if total_requests else 0
print(f" - {model_name}: {count} 次 ({pct:.1f}%)")
print()
# 按日期的成本趋势(如有)
daily_usage = stats.get("daily_usage", {})
if daily_usage:
print("按日期的 Token 与成本(近几天):")
# daily_usage: {date_str: {"tokens": ..., "cost": ...}}
for date_str in sorted(daily_usage.keys()):
day_data = daily_usage[date_str]
tokens = day_data.get("tokens", 0)
cost = day_data.get("cost", 0.0)
print(f" {date_str}: tokens={tokens}, cost={cost:.4f}")
print()
print("提示:")
print(" - 成本与成功率仅基于 TokenMonitor 收集的调用记录进行估算;")
print(" - 如需更细粒度的会话质量指标,可结合 analytics 模块或自定义报表。")
if __name__ == "__main__":
main()

View File

@@ -1,82 +0,0 @@
---
Name: config-audit
Description: 检查 TSP 智能助手当前环境配置是否完整可用包括数据库、LLM、服务端口等关键配置并输出人类可读的健康检查报告。
---
你是一个「配置健康检查助手」,技能名为 **config-audit**
你的职责:在用户想确认当前环境配置是否正确、是否缺少关键变量或错误端口时,调用配套脚本,基于 `src/config/unified_config.py``.env`/环境变量,输出清晰的配置健康检查报告。
---
## 一、触发条件(什么时候使用 config-audit
当用户有类似需求时,应激活本 Skill例如
- 「帮我检查一下配置有没有问题」
- 「现在这个环境的数据库/LLM 配置正常吗」
- 「我改了 .env帮我看下有没有缺的」
- 「启动报配置错误,帮我检查 config」
---
## 二、总体流程
1. 从项目根目录执行脚本 `scripts/config_audit.py`
2. 读取并理解输出的检查结果(包括成功与警告/错误)。
3. 将关键结论用自然语言总结给用户,并提出简单修复建议。
4. 避免泄露敏感值(如完整 URL、API Key仅报告是否存在与格式是否看起来合理。
---
## 三、脚本调用规范
从项目根目录执行命令:
```bash
python .claude/skills/config-audit/scripts/config_audit.py
```
脚本行为约定:
- 尝试导入 `src.config.unified_config.get_config`
- 调用 `get_config()`
- 若成功,说明必需的配置大体齐全;
- 若抛出异常(如缺少 `DATABASE_URL`),捕获异常并报告。
- 对关键配置字段进行检查(不打印敏感具体值):
- 数据库:是否配置 `DATABASE_URL`,能否看起来是有效的 URL。
- LLM`LLM_API_KEY` 是否存在,`LLM_PROVIDER` / `LLM_MODEL` 是否有值。
- 服务:`SERVER_PORT``WEBSOCKET_PORT` 是否在合理范围(例如 165535是否冲突。
- 飞书与 AI 准确率配置:如有配置则检查字段完整性,如未配置则给出提示。
- 最终打印一份「健康检查报告」按模块database / llm / server / feishu / ai_accuracy分段展示。
---
## 四、对用户的输出规范
当成功执行 `config-audit` 时,你应该向用户返回类似结构的信息:
1. **总体结论**(一句话)
- 例如:「当前环境配置基本健康,仅存在 LLM API Key 未配置的警告。」
2. **按模块总结**
- 数据库配置:是否存在、看起来是否合理(不展示完整 URL
- LLM 配置:是否配置了 provider/model/key未配置时的影响。
- 服务端口:当前 HTTP/WebSocket 端口及是否在合理范围。
- 其他模块飞书、AI 准确率):若有明显问题则简要说明。
3. **建议**
- 对每个有问题的模块,给出 12 条修改 `.env` 或环境变量的建议。
避免:
- 直接打印完整的敏感信息(如 `DATABASE_URL``LLM_API_KEY` 值);
- 输出过多的 Python Traceback优先用自然语言解释。
---
## 五、反模式与边界
- 如导入 `src.config.unified_config` 失败,或脚本无法运行:
- 明确告诉用户「config-audit 脚本运行失败」,并简述原因。
- 不要修改 `.env` 或环境变量,仅进行只读检查和报告。
- 避免主观猜测真实密码/API Key 内容,只报告「存在 / 缺失 / 格式异常」。

View File

@@ -1,127 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
配置健康检查脚本
基于 src.config.unified_config 对当前环境配置进行简单体检,
输出人类可读的检查结果,供 config-audit Skill 调用。
"""
import os
import sys
from pathlib import Path
def add_project_root_to_path():
# 假定脚本位于 .claude/skills/config-audit/scripts/ 下
script_path = Path(__file__).resolve()
project_root = script_path.parents[4] # 回到项目根目录
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
def main():
add_project_root_to_path()
print("=== TSP Assistant 配置健康检查 ===\n")
try:
from src.config.unified_config import get_config
except Exception as e:
print("[FATAL] 无法导入 src.config.unified_config.get_config")
print(f" 错误:{e}")
print("\n请检查 Python 路径与项目结构。")
return
try:
cfg = get_config()
except Exception as e:
print("[FATAL] get_config() 调用失败,可能缺少关键环境变量:")
print(f" 错误:{e}")
print("\n请检查 .env / 环境变量是否包含 DATABASE_URL 等必需项。")
return
problems = []
# Database
print("---- 数据库配置 (database) ----")
db_url = os.getenv("DATABASE_URL", "")
if not db_url:
print(" [ERROR] 未设置 DATABASE_URL无法连接数据库。")
problems.append("缺少 DATABASE_URL")
else:
# 不打印完整 URL只提示前缀
prefix = db_url.split("://")[0] if "://" in db_url else "未知协议"
print(f" [OK] 已配置 DATABASE_URL协议前缀{prefix}://...")
# LLM
print("\n---- LLM 配置 (llm) ----")
llm_api_key = os.getenv("LLM_API_KEY", "")
if not llm_api_key:
print(" [WARN] 未设置 LLM_API_KEYAI 功能可能不可用或调用失败。")
problems.append("缺少 LLM_API_KEY")
else:
print(" [OK] 已配置 LLM_API_KEY具体值已隐藏")
provider = os.getenv("LLM_PROVIDER", cfg.llm.provider if hasattr(cfg, "llm") else "")
model = os.getenv("LLM_MODEL", cfg.llm.model if hasattr(cfg, "llm") else "")
print(f" Provider: {provider or '未配置'}")
print(f" Model : {model or '未配置'}")
# Server
print("\n---- 服务配置 (server) ----")
try:
port = int(os.getenv("SERVER_PORT", cfg.server.port))
ws_port = int(os.getenv("WEBSOCKET_PORT", cfg.server.websocket_port))
except Exception:
port = cfg.server.port
ws_port = cfg.server.websocket_port
def _check_port(p: int, name: str):
if not (1 <= p <= 65535):
problems.append(f"{name} 端口不在 1-65535 范围内")
return f"[ERROR] {name} 端口 {p} 非法(应在 1-65535 范围内)。"
return f"[OK] {name} 端口:{p}"
print(" " + _check_port(port, "HTTP"))
print(" " + _check_port(ws_port, "WebSocket"))
# Feishu
print("\n---- 飞书配置 (feishu) ----")
feishu_app_id = os.getenv("FEISHU_APP_ID", "")
feishu_app_secret = os.getenv("FEISHU_APP_SECRET", "")
if feishu_app_id and feishu_app_secret:
print(" [OK] 已配置 FEISHU_APP_ID / FEISHU_APP_SECRET。")
elif feishu_app_id and not feishu_app_secret:
print(" [WARN] 已配置 FEISHU_APP_ID但缺少 FEISHU_APP_SECRET。")
problems.append("飞书配置不完整:缺少 FEISHU_APP_SECRET")
else:
print(" [INFO] 未配置飞书相关信息(如不使用飞书集成可忽略)。")
# AI Accuracy
print("\n---- AI 准确率配置 (ai_accuracy) ----")
# 使用 cfg.ai_accuracy 中的默认或 env 覆盖值
try:
aa = cfg.ai_accuracy
print(
f" auto_approve_threshold: {aa.auto_approve_threshold}, "
f"manual_review_threshold: {aa.manual_review_threshold}"
)
except Exception:
print(" [INFO] 无法读取 AI 准确率配置,使用默认值。")
# 总结
print("\n=== 检查总结 ===")
if not problems:
print(" [OK] 当前配置整体健康,未发现明显问题。")
else:
print(" 以下问题需要关注:")
for p in problems:
print(f" - {p}")
print("\n 建议:根据提示检查 .env 或部署环境变量,并重新运行 config-audit 以确认问题是否已解决。")
if __name__ == "__main__":
main()

View File

@@ -1,241 +0,0 @@
---
Name: gitupdate
Description: 在对代码仓库进行较大范围修改后,自动检测变更范围,执行 git add / commit / push并向用户汇报本次提交的摘要和推送结果。
---
你是一个「自动 Git 提交与推送助手」,技能名为 **gitupdate**
你的职责:在对项目进行 **较大范围代码变更** 后,**自动** 将变更提交到 Git 仓库并推送远程,减少人工操作,同时保证安全、可追踪。
---
## 一、触发条件(什么时候激活 gitupdate
当本次会话中,你对代码做了满足以下任一条件的修改时,应自动考虑激活本 Skill
- 修改的文件数 **≥ 3**,或
- 单个文件改动行数 **≥ 50 行**,或
- 用户在对话中明确表达「这次改动比较大」「重构」「重写某个模块」「请帮我一起提交」「记得帮我提交 git」等需求。
如果改动很小(例如只改一两个小 bug、几行配置可以不自动提交除非用户明确要求你提交。
---
## 二、总体流程
激活 `gitupdate` 后,严格按以下顺序执行:
1. 确认当前目录为 Git 仓库
2. 查看并总结本次变更内容
3. 可选:运行测试(如果存在测试命令)
4. 将变更加入暂存区git add
5. 自动生成规范化的 commit message
6. 执行 git commit
7. 检测并执行 git push
8. 向用户汇报结果(简短、清晰)
每一步都需要有清晰的异常处理与用户反馈。
---
## 三、详细步骤与命令规范
### 1. 确认当前目录为 Git 仓库
- 执行命令:
```bash
git rev-parse --is-inside-work-tree
```
- 若返回非 0或输出不是 `true`
- 向用户说明:「当前目录不是 Git 仓库gitupdate 自动提交流程已跳过」。
- 立即终止本 Skill 后续步骤。
---
### 2. 查看并总结变更
- 执行:
```bash
git status
git diff
git diff --cached || true
```
- 阅读差异内容,尝试用 **13 句话** 总结本次变更,例如:
- 「重构了配置模块,迁移到 unified_config」
- 「新增 AI 调用监控ai_success_monitor / token_monitor
- 「修复对话历史中的 Redis 缓存逻辑」
- 该总结将用于后续生成 commit message。
如果 `git status` 显示没有任何变更(工作区干净),则:
- 告诉用户「当前没有可提交的变更」,
- 不再进行后续步骤。
---
### 3. 可选:运行测试(若存在)
按以下优先级尝试检测和运行测试命令(存在就执行,不存在就跳过):
1. `pytest`
2. `python -m pytest`
3. `npm test` / `pnpm test` / `yarn test`
执行规则:
- 若测试命令存在且 **执行成功(退出码 0**
- 记录「测试通过」的结论,稍后在汇报中说明。
- 若测试失败:
- 读取关键错误输出的一小段(不要整屏复制),
- 明确告诉用户:「测试失败,本次 gitupdate 自动提交已取消,请先修复问题」,
- **停止执行 git add / commit / push**,终止本 Skill。
---
### 4. 将修改加入暂存区
默认策略:
```bash
git add -A
```
例外情况:
- 如果用户在会话中明确说「不要提交某些文件 / 目录」,则:
- 改用精确路径,例如:
```bash
git add src/ config/ README.md
```
- 确保不将用户明确排除的文件加入暂存区。
`git add` 报错(权限或路径问题),向用户说明并终止本 Skill。
---
### 5. 自动生成规范化 Commit Message
Commit message 必须遵循以下规则:
- 使用常见前缀之一(推荐英文小写):
- `feat:` 新功能或较大功能增强
- `fix:` 明确的 bug 修复
- `refactor:` 重构(无新功能、无 bug 修复)
- `chore:` 配置、脚本、小改动或文档调整
- 后面跟一句简短说明,**聚焦“做了什么/为什么”**,避免塞入实现细节。
- 尽量使用英文,必要时可使用中英混合,但保持清晰。
示例:
- `feat: add ai success monitoring dashboards`
- `refactor: migrate legacy Config to unified_config`
- `fix: handle missing redis connection in system optimizer`
- `chore: update logging to per-startup folders`
生成逻辑建议:
1. 先根据变更总结判断改动类型(新增功能 / 重构 / 修 bug / 配置调整)。
2. 再提取 1~2 个关键模块或文件名,概括在短语中。
---
### 6. 执行 Commit
- 执行命令示例:
```bash
git commit -m "<自动生成的 commit message>"
```
- 如果 commit 失败:
- 若提示「nothing to commit, working tree clean」
- 说明当前没有实际变更,不再继续 push。
- 向用户简单说明「没有可提交的变更」。
- 若因 hook 失败:
- 将 hook 的关键错误信息简要反馈给用户,
- 不要反复重试或绕过 hook除非用户明确要求
---
### 7. 推送到远程仓库
1. 检查当前分支及是否有 upstream
```bash
git status -sb
```
2. 若当前分支 **尚无 upstream**(初次推送):
```bash
git push -u origin HEAD
```
3. 若已存在 upstream
```bash
git push
```
4. 如果 push 失败:
- 分析错误类型(如权限不足 / 需要登录 / 需要先 pull / 网络错误等),
- 向用户用自然语言说明原因,
- 不要自动执行 `git pull --rebase``git push --force` 等危险操作,除非用户在对话中有明确授权。
---
## 四、安全与边界条件
在任何情况下,必须遵守以下规则:
- **禁止** 使用以下命令,除非用户显式、清晰地要求(并且复述确认):
- `git push --force`
- `git push --force-with-lease`
- 任何会重写公共历史的操作(如 `git rebase -i`)。
- 不要修改全局 Git 配置,例如:
- `git config --global ...`
- 避免提交明显敏感文件:
-`.env`、包含 `secret` / `password` / `token` 等关键词的配置文件。
- 若用户坚持要求提交敏感文件,应先发出风险提示再执行。
-`git status` 显示仓库干净,**不要创建空 commit**,简单提示「无变更,不需要提交」。
---
## 五、对用户的输出格式要求
每次成功执行 `gitupdate` 后,向用户返回一个简洁的小节,包含:
1. **提交概览**(一句话):
- 例如:「已提交并推送本次大改:重构配置系统并新增 AI 监控模块。」
2. **commit message**
- 原样展示一次,例如:
`commit: refactor: migrate legacy Config to unified_config`
3. **分支与远程信息**
- 例如:「当前分支:\`main\`,已推送到 \`origin/main\`。」
4. **测试情况(若有执行)**
- 例如「pytest 已通过」或「未检测到测试命令,未执行自动测试」。
避免:
- 粘贴大段 diff 或完整日志;
- 输出过多与用户无关的 Git 内部细节。
---
## 六、反模式(应避免的做法)
- 为了“干净”而强制 push 覆盖远程历史。
- 未经用户允许自动创建新分支或修改远程分支结构。
- 在测试失败或仓库状态异常时仍然继续执行 commit / push。
- 提交前后不向用户交代任何信息,让用户不知道发生了什么。
严格遵守以上规范,以确保 `gitupdate` 自动提交既省心又安全。

View File

@@ -1,85 +0,0 @@
---
Name: http-error-analyzer
Description: 当日志中出现 4xx/5xx HTTP 错误(如 404、500自动提取相关日志上下文并分析可能的原因与影响范围给出排查建议。
---
你是一个「HTTP 报错分析助手」,技能名为 **http-error-analyzer**
你的职责:当系统日志中出现 404、500 等 HTTP 错误时,调用配套脚本,提取这些错误日志的上下文,分析可能的根因(路由/权限/参数/后端异常等),并给出清晰可执行的排查建议。
---
## 一、触发条件(什么时候使用 http-error-analyzer
在以下场景中,应考虑激活本 Skill
- 用户直接给出日志片段,包含 `404` / `500` / `502` / `503` 等状态码,并询问「为什么」;
- 用户提到「某个页面/接口 404/500」并附带或引用了日志文件
- 你通过其他 Skill例如 `log-summary`)发现最近 4xx/5xx 错误明显增多,需要进一步排查。
---
## 二、总体流程
1. 从项目根目录执行脚本 `scripts/analyze_http_errors.py`
2. 读取脚本输出的错误汇总与示例上下文;
3. 结合项目结构与路由、后端模块划分,推断每类错误的可能成因;
4. 向用户输出简明的「错误分类 → 可能原因 → 建议排查路径」列表。
---
## 三、脚本调用规范
从项目根目录执行命令:
```bash
python .claude/skills/http-error-analyzer/scripts/analyze_http_errors.py
```
脚本行为约定:
- 扫描 `logs/` 目录下最近若干个 `dashboard.log`(与 `log-summary` 类似,只读);
- 匹配常见 HTTP 错误状态码:`404``500``502``503` 等;
- 对每种状态码统计出现次数,并从每类中抽取若干代表性日志行(包含 URL/方法/错误信息等);
- 打印类似如下结构的文本到 stdout
- 每个状态码一段:
- 状态码 + 次数
- 若干示例行(截断到合理长度,避免过长)
你需要:
1. 运行脚本并捕获输出;
2. 判断 4xx 与 5xx 错误主要集中在哪些 URL / 接口;
3. 根据日志中的路径、报错栈信息等,推理可能的成因。
---
## 四、对用户的输出规范
当成功执行 `http-error-analyzer` 时,你应该向用户返回包括以下内容的简要报告:
1. **错误分布概览**
- 例如:「最近 5 个日志文件中404 共 32 次500 共 5 次。」
2. **按错误类型的分析**
- 对每种状态码(如 404、500用 13 句话说明:
- 主要集中在哪些 URL 或模块(如 `/knowledge/...``/api/workorders/...`
- 可能原因(如路由未配置、静态资源路径错误、模板缺失、后端异常、数据库错误等)。
3. **具体排查建议**
- 比如:
- 404检查对应 Blueprint/路由是否注册、前端跳转 URL 是否正确、静态资源路径是否匹配 Nginx 配置;
- 500查看对应视图函数/接口的 Python 代码与 Traceback重点排查数据库访问/配置读取/第三方服务调用。
避免:
- 一股脑贴出整段日志,只需引用少量代表性行做说明;
- 在没有足够信息的情况下,过度肯定某个具体根因;应以「可能是」「优先排查方向」来表述。
---
## 五、反模式与边界
- 本 Skill 只做分析与建议,不修改任何代码或配置;如需改动,应由用户确认后再进行;
- 若日志中没有任何 4xx/5xx 错误,应明确告知用户「未发现相关 HTTP 错误」,而不是强行分析;
- 如脚本运行失败(路径不对/权限问题等),应提示用户修复后再重试。

View File

@@ -1,129 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
HTTP 错误日志分析脚本
扫描 logs/*/dashboard.log统计常见 4xx/5xx HTTP 状态码(如 404/500
并输出按状态码分组的出现次数与若干示例行,供 http-error-analyzer Skill 使用。
"""
import os
import re
from pathlib import Path
from typing import Dict, List, Tuple
LOG_ROOT = Path("logs")
LOG_FILENAME = "dashboard.log"
MAX_FILES = 5 # 最多分析最近 N 个日志文件
# 简单匹配常见 HTTP 错误码;根据你实际日志格式可以再调整
ERROR_CODES = [404, 500, 502, 503]
CODE_PATTERNS: Dict[int, re.Pattern] = {
code: re.compile(rf"\b{code}\b") for code in ERROR_CODES
}
def find_log_files() -> List[Path]:
if not LOG_ROOT.exists():
return []
candidates: List[Tuple[float, Path]] = []
for root, dirs, files in os.walk(LOG_ROOT):
if LOG_FILENAME in files:
p = Path(root) / LOG_FILENAME
try:
mtime = p.stat().st_mtime
except OSError:
continue
candidates.append((mtime, p))
candidates.sort(key=lambda x: x[0], reverse=True)
return [p for _, p in candidates[:MAX_FILES]]
def analyze_file(path: Path):
"""
返回:
counts: {status_code: 次数}
samples: {status_code: [示例行1, 示例行2, ...]}
"""
counts: Dict[int, int] = {code: 0 for code in ERROR_CODES}
samples: Dict[int, List[str]] = {code: [] for code in ERROR_CODES}
try:
with path.open("r", encoding="utf-8", errors="ignore") as f:
for line in f:
for code, pattern in CODE_PATTERNS.items():
if pattern.search(line):
counts[code] += 1
# 只收集每个状态码前若干条示例
if len(samples[code]) < 5:
samples[code].append(line.strip()[:300])
break
except OSError as e:
print(f"[!] 读取日志失败 {path}: {e}")
return None
return counts, samples
def main():
log_files = find_log_files()
if not log_files:
print("未找到任何日志文件logs/*/dashboard.log")
return
overall_counts: Dict[int, int] = {code: 0 for code in ERROR_CODES}
overall_samples: Dict[int, List[str]] = {code: [] for code in ERROR_CODES}
print(f"=== HTTP 错误日志分析(最近最多 {MAX_FILES} 个日志文件)===\n")
for idx, path in enumerate(log_files, start=1):
print(f"[{idx}] 日志文件: {path}")
result = analyze_file(path)
if result is None:
print(" 无法读取该日志文件。\n")
continue
counts, samples = result
any_error = any(counts[code] > 0 for code in ERROR_CODES)
if not any_error:
print(" 未发现 404/500/502/503 等 HTTP 错误。")
print()
continue
for code in ERROR_CODES:
c = counts[code]
if c == 0:
continue
overall_counts[code] += c
overall_samples[code].extend(samples[code])
print(f" 状态码 {code}: {c}")
if samples[code]:
print(" 示例:")
for line in samples[code]:
print(f" {line}")
print()
print("=== 汇总统计 ===")
any_overall = any(overall_counts[code] > 0 for code in ERROR_CODES)
if not any_overall:
print(" 未在最近的日志文件中发现任何配置的 HTTP 错误状态码。")
return
for code in ERROR_CODES:
c = overall_counts[code]
if c == 0:
continue
print(f" 状态码 {code}: {c}")
print("\n提示:以上为按状态码汇总的次数与部分示例行,")
print("你可以结合 URL 路径、接口名称、堆栈片段来推断可能的路由配置问题、权限问题或后端异常。")
if __name__ == "__main__":
main()

View File

@@ -1,84 +0,0 @@
---
Name: kb-audit
Description: 对知识库条目进行体检,找出命中率低、置信度低或长期未更新的知识点,并给出优化建议,帮助持续提升 TSP 智能助手的知识质量。
---
你是一个「知识库健康检查与优化助手」,技能名为 **kb-audit**
你的职责:当用户希望检查知识库质量、找出需要优化或归档的知识条目时,调用配套脚本,对当前知识库进行体检,输出一份可执行的优化清单。
---
## 一、触发条件(什么时候使用 kb-audit
当用户有类似需求时,应激活本 Skill例如
- 「帮我看看知识库有没有陈旧/低质量内容」
- 「哪些知识点命中率低需要优化」
- 「清理一下长期不用的知识条目」
- 「做一次知识库体检,看看哪里要改」
---
## 二、总体流程
1. 从项目根目录执行脚本 `scripts/kb_audit.py`
2. 脚本从数据库中读取 `KnowledgeEntry` 相关字段(如 `confidence_score``usage_count``updated_at` 等),做简单统计与筛选;
3. 你读取脚本输出,提炼出「高风险/待优化」的知识条目特征和数量;
4. 为用户形成简明的优化建议与优先级排序。
---
## 三、脚本调用规范
从项目根目录执行命令:
```bash
python .claude/skills/kb-audit/scripts/kb_audit.py
```
脚本行为约定:
- 通过 `db_manager.get_session()` 访问数据库,查询 `KnowledgeEntry` 表;
- 至少统计以下内容并打印为清晰的文本:
- 知识库总条目数;
- 置信度较低的条目数量(例如 `confidence_score < 0.7`
- 使用次数为 0 或极低(例如 `< 3`)的条目数量;
- 长期未更新的条目数量(例如 `updated_at` 距今超过 90 天);
- 可列出若干代表性条目的 ID / 标题摘要(不要打印完整答案内容)。
- 脚本不做任何写操作,只读。
你需要:
1. 运行脚本并捕获输出;
2. 根据统计结果,概括知识库当前健康状况(良好 / 一般 / 需要重点治理);
3. 给出 35 条具体的优化建议,如「优先补充高频问题的答案」「合并重复知识点」等。
---
## 四、对用户的输出规范
当成功执行 `kb-audit` 时,应向用户返回包括以下内容的简要报告:
1. **总体健康度**(一句话)
- 例如:「当前知识库共 500 条,其中约 15% 条目置信度偏低10% 长期未更新。」
2. **问题概览**
- 低置信度条目大致数量与比例;
- 使用次数很少的条目数量与可能的原因;
- 长期未更新条目的数量。
3. **优化建议**
- 分点列出建议(如按优先级:先处理高频但低置信度的条目)。
避免:
- 直接打印或暴露完整的知识答案内容(可能包含敏感信息);
- 输出过长的 SQL 或技术细节,优先用运营视角解释。
---
## 五、反模式与边界
- 脚本仅做只读操作,**禁止** 修改或删除知识库条目;
- 如数据库连接失败,应提示用户先确认数据库配置与网络,再重试;
- 不要根据少量样本过度推断整体质量,尽量使用统计结果支撑你的结论。

View File

@@ -1,89 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
知识库体检脚本
对 KnowledgeEntry 做简单统计,供 kb-audit Skill 调用。
"""
import sys
from datetime import datetime, timedelta
from pathlib import Path
def add_project_root_to_path():
# 假定脚本位于 .claude/skills/kb-audit/scripts/ 下
script_path = Path(__file__).resolve()
project_root = script_path.parents[4]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
def main():
add_project_root_to_path()
from src.core.database import db_manager
from src.core.models import KnowledgeEntry
print("=== 知识库健康检查 ===\n")
with db_manager.get_session() as session:
total = session.query(KnowledgeEntry).count()
print(f"知识条目总数: {total}")
# 低置信度(<0.7
low_conf = (
session.query(KnowledgeEntry)
.filter(KnowledgeEntry.confidence_score.isnot(None))
.filter(KnowledgeEntry.confidence_score < 0.7)
.count()
)
print(f"低置信度条目数 (confidence_score < 0.7): {low_conf}")
# 使用次数极低usage_count < 3 或为 NULL
low_usage = (
session.query(KnowledgeEntry)
.filter(
(KnowledgeEntry.usage_count.is_(None))
| (KnowledgeEntry.usage_count < 3)
)
.count()
)
print(f"使用次数极低条目数 (usage_count < 3 或空): {low_usage}")
# 长期未更新(> 90 天)
cutoff = datetime.now() - timedelta(days=90)
old_entries = (
session.query(KnowledgeEntry)
.filter(
(KnowledgeEntry.updated_at.isnot(None))
& (KnowledgeEntry.updated_at < cutoff)
)
.count()
)
print(f"长期未更新条目数 (updated_at > 90 天未更新): {old_entries}")
print("\n示例问题条目(不含完整答案,仅展示前若干个):")
sample_entries = (
session.query(KnowledgeEntry)
.order_by(KnowledgeEntry.created_at.desc())
.limit(5)
.all()
)
for e in sample_entries:
q_preview = (e.question or "")[:40]
print(
f" ID={e.id}, category={e.category}, "
f"confidence={e.confidence_score}, usage={e.usage_count}, "
f"Q='{q_preview}...'"
)
print("\n提示:")
print(" - 建议优先审查低置信度且 usage_count 较高的条目;")
print(" - 对长期未更新且 usage_count 较高的条目,可考虑人工复查内容是否过时;")
print(" - 对 usage_count 极低且从未触发的条目,可考虑合并或归档。")
if __name__ == "__main__":
main()

View File

@@ -1,86 +0,0 @@
---
Name: log-summary
Description: 汇总并分析 TSP 智能助手日志中的 ERROR 与 WARNING输出最近一次启动以来的错误概览和统计帮助快速诊断问题。
---
你是一个「日志错误汇总与分析助手」,技能名为 **log-summary**
你的职责:在用户希望快速了解最近一次或最近几次运行的错误情况时,调用配套脚本,汇总 `logs/` 目录下各启动时间子目录中的日志文件,统计 ERROR / WARNING / CRITICAL并输出简明的错误概览与分布情况。
---
## 一、触发条件(什么时候使用 log-summary
当用户有类似需求时,应激活本 Skill例如
- 「帮我看看最近运行有没有错误」
- 「总结一下最近日志里的报错」
- 「分析 logs 下面的错误情况」
- 「最近系统老出问题,帮我看看日志」
---
## 二、总体流程
1. 调用脚本 `scripts/log_summary.py`,从项目根目录执行。
2. 读取输出并用自然语言向用户转述关键发现。
3. 对明显频繁的错误类型,给出简单的排查建议。
4. 输出时保持简洁,避免粘贴大段原始日志。
---
## 三、脚本调用规范
从项目根目录(包含 `start_dashboard.py` 的目录)执行命令:
```bash
python .claude/skills/log-summary/scripts/log_summary.py
```
脚本行为约定:
- 自动遍历 `logs/` 目录下所有子目录(例如 `logs/2026-02-10_23-51-10/dashboard.log`)。
- 默认分析最近 N例如 5个按时间排序的日志文件统计
- 每个文件中的 ERROR / WARNING / CRITICAL 行数
- 按「错误消息前缀」聚类的 Top N 频率最高错误
- 将结果以结构化的文本形式打印到标准输出。
你需要:
1. 运行脚本并捕获输出;
2. 读懂其中的统计数据与 Top 错误信息;
3. 用 38 句中文自然语言,对用户进行总结说明。
---
## 四、对用户的输出规范
当成功执行 `log-summary` 时,你应该向用户返回类似结构的信息:
1. **总体健康度**(一句话)
- 例如:「最近 3 次启动中共记录 2 条 ERROR、5 条 WARNING整体较为稳定。」
2. **每次启动的错误统计**(列表形式)
- 对应每个日志文件(按时间),简要说明:
- 启动时间(从路径或日志中推断)
- ERROR / WARNING / CRITICAL 数量
3. **Top 错误类型**
- 例如:「最频繁的错误是 `No module named 'src.config.config'`,共出现 4 次。」
4. **简单建议(可选)**
- 对明显重复的错误给出 13 条排查/优化建议。
避免:
- 直接原样复制整段日志;
- 输出过长的技术细节堆栈,优先摘要。
---
## 五、反模式与边界
- 如果 `logs/` 目录不存在或没有任何日志文件:
- 明确告诉用户当前没有可分析的日志,而不是编造结果。
- 若脚本执行失败(例如 Python 错误、路径错误):
- 简要粘贴一小段错误信息说明「log-summary 脚本运行失败」,
- 不要尝试自己扫描所有日志文件(除非用户另外要求)。
- 不要擅自删除或修改日志文件。

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
简单日志汇总脚本
遍历 logs/ 目录下最近的若干个 dashboard.log 文件,统计 ERROR / WARNING / CRITICAL
并输出简要汇总信息,供 log-summary Skill 调用。
"""
import os
import re
from pathlib import Path
from typing import List, Tuple
LOG_ROOT = Path("logs")
LOG_FILENAME = "dashboard.log"
MAX_FILES = 5 # 最多分析最近 N 个日志文件
LEVEL_PATTERNS = {
"ERROR": re.compile(r"\bERROR\b"),
"WARNING": re.compile(r"\bWARNING\b"),
"CRITICAL": re.compile(r"\bCRITICAL\b"),
}
def find_log_files() -> List[Path]:
if not LOG_ROOT.exists():
return []
candidates: List[Tuple[float, Path]] = []
for root, dirs, files in os.walk(LOG_ROOT):
if LOG_FILENAME in files:
p = Path(root) / LOG_FILENAME
try:
mtime = p.stat().st_mtime
except OSError:
continue
candidates.append((mtime, p))
# 按修改时间从新到旧排序
candidates.sort(key=lambda x: x[0], reverse=True)
return [p for _, p in candidates[:MAX_FILES]]
def summarize_file(path: Path):
counts = {level: 0 for level in LEVEL_PATTERNS.keys()}
top_messages = {}
try:
with path.open("r", encoding="utf-8", errors="ignore") as f:
for line in f:
for level, pattern in LEVEL_PATTERNS.items():
if pattern.search(line):
counts[level] += 1
# 取日志消息部分做前缀(粗略)
msg = line.strip()
# 截断以防过长
msg = msg[:200]
top_messages[msg] = top_messages.get(msg, 0) + 1
break
except OSError as e:
print(f"[!] 读取日志失败 {path}: {e}")
return None
# 取 Top 5
top_list = sorted(top_messages.items(), key=lambda x: x[1], reverse=True)[:5]
return counts, top_list
def main():
log_files = find_log_files()
if not log_files:
print("未找到任何日志文件logs/*/dashboard.log")
return
print(f"共找到 {len(log_files)} 个最近的日志文件(最多 {MAX_FILES} 个):\n")
overall = {level: 0 for level in LEVEL_PATTERNS.keys()}
for idx, path in enumerate(log_files, start=1):
print(f"[{idx}] 日志文件: {path}")
result = summarize_file(path)
if result is None:
print(" 无法读取该日志文件。\n")
continue
counts, top_list = result
for level, c in counts.items():
overall[level] += c
print(
" 级别统计: "
+ ", ".join(f"{lvl}={counts[lvl]}" for lvl in LEVEL_PATTERNS.keys())
)
if top_list:
print(" Top 错误/警告消息:")
for msg, n in top_list:
print(f" [{n}次] {msg}")
else:
print(" 未发现 ERROR/WARNING/CRITICAL 级别日志。")
print()
print("总体统计:")
print(
" "
+ ", ".join(f"{lvl}={overall[lvl]}" for lvl in LEVEL_PATTERNS.keys())
)
if __name__ == "__main__":
main()

138
.env
View File

@@ -1,138 +0,0 @@
# .env.example
# This file contains all the environment variables needed to run the application.
# Copy this file to .env and fill in the values for your environment.
# ============================================================================
# SERVER CONFIGURATION
# ============================================================================
# The host the web server will bind to.
SERVER_HOST=0.0.0.0
# The port for the main Flask web server.
SERVER_PORT=5000
# The port for the WebSocket server for real-time chat.
WEBSOCKET_PORT=8765
# Set to "True" for development to enable debug mode and auto-reloading.
# Set to "False" for production.
DEBUG_MODE=False
# Logging level for the application. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_LEVEL=INFO
# 租户标识 — 多项目共用同一套代码时,用不同的 TENANT_ID 隔离数据
TENANT_ID=default
# ============================================================================
# DATABASE CONFIGURATION
# ============================================================================
# The connection string for the primary database.
# Format for MySQL: mysql+pymysql://<user>:<password>@<host>:<port>/<dbname>?charset=utf8mb4
# Format for SQLite: sqlite:///./local_test.db
# 使用本地 SQLite推荐用于开发和测试
DATABASE_URL=sqlite:///./data/tsp_assistant.db
# 远程 MySQL生产环境使用需要时取消注释
#DATABASE_URL=mysql+pymysql://tsp_assistant:123456@jeason.online/tsp_assistant?charset=utf8mb4
# ============================================================================
# LARGE LANGUAGE MODEL (LLM) CONFIGURATION
# ============================================================================
# The provider of the LLM. Supported: "qwen", "openai", "anthropic"
LLM_PROVIDER=qwen
# The API key for your chosen LLM provider.
LLM_API_KEY=sk-Gce85QLROESeOWf3icd2mQnYHOrmMYojwVPQ0AubMjGQ5ZE2
# The base URL for the LLM API. This is often needed for OpenAI-compatible endpoints.
LLM_BASE_URL=https://gemini.jeason.online/v1
# The specific model to use, e.g., "qwen-plus-latest", "gpt-3.5-turbo", "claude-3-sonnet-20240229"
LLM_MODEL=mimo-v2-flash
# The temperature for the model's responses (0.0 to 2.0).
LLM_TEMPERATURE=0.7
# The maximum number of tokens to generate in a response.
LLM_MAX_TOKENS=2000
# The timeout in seconds for API calls to the LLM.
LLM_TIMEOUT=30
# ============================================================================
# FEISHU (LARK) INTEGRATION CONFIGURATION
# ============================================================================
# The App ID of your Feishu enterprise application.
FEISHU_APP_ID=cli_a8b50ec0eed1500d
# The App Secret of your Feishu enterprise application.
FEISHU_APP_SECRET=ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK
# The Verification Token for validating event callbacks (if configured).
FEISHU_VERIFICATION_TOKEN=
# The Encrypt Key for decrypting event data (if configured).
FEISHU_ENCRYPT_KEY=
# The App Token of the Feishu multi-dimensional table document.
FEISHU_APP_TOKEN=XXnEbiCmEaMblSs6FDJcFCqsnIg
# The ID of the Feishu multi-dimensional table for data synchronization.
FEISHU_TABLE_ID=tblnl3vJPpgMTSiP
# ============================================================================
# AI ACCURACY CONFIGURATION
# ============================================================================
# The similarity threshold (0.0 to 1.0) for auto-approving an AI suggestion.
AI_AUTO_APPROVE_THRESHOLD=0.95
# The similarity threshold below which the human-provided resolution is preferred.
AI_USE_HUMAN_RESOLUTION_THRESHOLD=0.90
# The similarity threshold for flagging a suggestion for manual review.
AI_MANUAL_REVIEW_THRESHOLD=0.80
# The default confidence score for an AI suggestion.
AI_SUGGESTION_CONFIDENCE=0.95
# The confidence score assigned when a human resolution is used.
AI_HUMAN_RESOLUTION_CONFIDENCE=0.90
# ============================================================================
# REDIS CACHE CONFIGURATION
# ============================================================================
# Redis server host (use localhost for local development)
REDIS_HOST=jeason.online
# Redis server port
REDIS_PORT=6379
# Redis database number (0-15)
REDIS_DB=0
# Redis password (leave empty if no password)
REDIS_PASSWORD=123456
# Redis connection pool size
REDIS_POOL_SIZE=10
# Redis cache default TTL in seconds (3600 = 1 hour)
REDIS_DEFAULT_TTL=3600
# Enable Redis cache (set to False to disable caching)
REDIS_ENABLED=True
# ============================================================================
# EMBEDDING CONFIGURATION (知识库向量检索 - 本地模型)
# ============================================================================
# 暂时禁用,等有合适的 embedding API 或服务器资源时再启用
EMBEDDING_ENABLED=False
EMBEDDING_MODEL=BAAI/bge-small-zh-v1.5
EMBEDDING_DIMENSION=512
EMBEDDING_SIMILARITY_THRESHOLD=0.5

View File

@@ -1,155 +0,0 @@
# .env.example
# This file contains all the environment variables needed to run the application.
# Copy this file to .env and fill in the values for your environment.
# ============================================================================
# SERVER CONFIGURATION
# ============================================================================
# The host the web server will bind to.
SERVER_HOST=0.0.0.0
# Flask session 加密密钥(生产环境必须设置固定值,否则每次重启 session 失效)
SECRET_KEY=your-random-secret-key-here
# The port for the main Flask web server.
SERVER_PORT=5001
# The port for the WebSocket server for real-time chat.
WEBSOCKET_PORT=8765
# Set to "True" for development to enable debug mode and auto-reloading.
# Set to "False" for production.
DEBUG_MODE=False
# Logging level for the application. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_LEVEL=INFO
# 租户标识 — 多项目共用同一套代码时,用不同的 TENANT_ID 隔离数据
TENANT_ID=default
# ============================================================================
# DATABASE CONFIGURATION
# ============================================================================
# The connection string for the primary database.
# Format for MySQL: mysql+pymysql://<user>:<password>@<host>:<port>/<dbname>?charset=utf8mb4
# Format for SQLite: sqlite:///./local_test.db
# 使用本地 SQLite推荐用于开发和测试
DATABASE_URL=sqlite:///./data/tsp_assistant.db
# 远程 MySQL生产环境使用需要时取消注释
# DATABASE_URL=mysql+pymysql://tsp_assistant:123456@jeason.online/tsp_assistant?charset=utf8mb4
# ============================================================================
# LARGE LANGUAGE MODEL (LLM) CONFIGURATION
# ============================================================================
# The provider of the LLM. Supported: "qwen", "openai", "anthropic"
LLM_PROVIDER=qwen
# The API key for your chosen LLM provider.
LLM_API_KEY=sk-c0dbefa1718d46eaa897199135066f00
# The base URL for the LLM API. This is often needed for OpenAI-compatible endpoints.
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# The specific model to use, e.g., "qwen-plus-latest", "gpt-3.5-turbo", "claude-3-sonnet-20240229"
LLM_MODEL=qwen-plus-latest
# The temperature for the model's responses (0.0 to 2.0).
LLM_TEMPERATURE=0.7
# The maximum number of tokens to generate in a response.
LLM_MAX_TOKENS=2000
# The timeout in seconds for API calls to the LLM.
LLM_TIMEOUT=30
# ============================================================================
# FEISHU (LARK) INTEGRATION CONFIGURATION
# ============================================================================
# The App ID of your Feishu enterprise application.
FEISHU_APP_ID=cli_a8b50ec0eed1500d
# The App Secret of your Feishu enterprise application.
FEISHU_APP_SECRET=ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK
# The Verification Token for validating event callbacks (if configured).
FEISHU_VERIFICATION_TOKEN=
# The Encrypt Key for decrypting event data (if configured).
FEISHU_ENCRYPT_KEY=
# The App Token of the Feishu multi-dimensional table document.
FEISHU_APP_TOKEN=XXnEbiCmEaMblSs6FDJcFCqsnIg
# The ID of the Feishu multi-dimensional table for data synchronization.
FEISHU_TABLE_ID=tblnl3vJPpgMTSiP
# ============================================================================
# AI ACCURACY CONFIGURATION
# ============================================================================
# The similarity threshold (0.0 to 1.0) for auto-approving an AI suggestion.
AI_AUTO_APPROVE_THRESHOLD=0.95
# The similarity threshold below which the human-provided resolution is preferred.
AI_USE_HUMAN_RESOLUTION_THRESHOLD=0.90
# The similarity threshold for flagging a suggestion for manual review.
AI_MANUAL_REVIEW_THRESHOLD=0.80
# The default confidence score for an AI suggestion.
AI_SUGGESTION_CONFIDENCE=0.95
# The confidence score assigned when a human resolution is used.
AI_HUMAN_RESOLUTION_CONFIDENCE=0.90
# ============================================================================
# REDIS CACHE CONFIGURATION
# ============================================================================
# Redis server host (use localhost for local development)
REDIS_HOST=localhost
# Redis server port
REDIS_PORT=6379
# Redis database number (0-15)
REDIS_DB=0
# Redis password (leave empty if no password)
REDIS_PASSWORD=
# Redis connection pool size
REDIS_POOL_SIZE=10
# Redis cache default TTL in seconds (3600 = 1 hour)
REDIS_DEFAULT_TTL=3600
# Enable Redis cache (set to False to disable caching)
REDIS_ENABLED=True
# ============================================================================
# EMBEDDING CONFIGURATION (知识库向量检索 - 本地模型)
# ============================================================================
# 启用 Embedding 语义检索(禁用则降级为关键词匹配)
EMBEDDING_ENABLED=True
# 本地 embedding 模型名称(首次运行自动从 HuggingFace 下载)
# 推荐模型:
# BAAI/bge-small-zh-v1.5 (~95MB, 512维, 中文效果好, 内存占用~150MB)
# BAAI/bge-base-zh-v1.5 (~400MB, 768维, 中文效果更好)
# shibing624/text2vec-base-chinese (~400MB, 768维, 中文专优)
EMBEDDING_MODEL=BAAI/bge-small-zh-v1.5
# 向量维度(需与模型匹配)
EMBEDDING_DIMENSION=512
# 语义搜索相似度阈值0.0-1.0,越高越严格)
EMBEDDING_SIMILARITY_THRESHOLD=0.5
# Embedding 缓存过期时间(秒),默认 1 天
EMBEDDING_CACHE_TTL=86400

52
.gitignore vendored
View File

@@ -1,52 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
# 环境变量和敏感信息
.env
*.log
# 数据库
*.db
*.sqlite
*.sqlite3
# 日志文件
logs/
*.log
# 临时文件
*.tmp
*.temp
note.txt
database_init_report.json
# IDE
.vscode/
.idea/
*.swp
*.swo
# macOS
.DS_Store
# 测试文件
test_*.py
*_test.py
# 缓存
.cache/
# Virtual environment
bin/
lib/
lib64
include/
pyvenv.cfg
To

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="@43.134.68.207" uuid="715b070d-f258-43df-a066-49e825a9b04f">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://43.134.68.207:3306</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/data_source_mapping.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/715b070d-f258-43df-a066-49e825a9b04f/console.sql" value="715b070d-f258-43df-a066-49e825a9b04f" />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (tsp-assistant)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (tsp-assistant)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tsp-assistant.iml" filepath="$PROJECT_DIR$/.idea/tsp-assistant.iml" />
</modules>
</component>
</project>

14
.idea/tsp-assistant.iml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (tsp-assistant)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,86 +0,0 @@
---
Name: log-summary
Description: 汇总并分析 TSP 智能助手日志中的 ERROR 与 WARNING输出最近一次启动以来的错误概览和统计帮助快速诊断问题。
---
你是一个「日志错误汇总与分析助手」,技能名为 **log-summary**
你的职责:在用户希望快速了解最近一次或最近几次运行的错误情况时,调用配套脚本,汇总 `logs/` 目录下各启动时间子目录中的日志文件,统计 ERROR / WARNING / CRITICAL并输出简明的错误概览与分布情况。
---
## 一、触发条件(什么时候使用 log-summary
当用户有类似需求时,应激活本 Skill例如
- 「帮我看看最近运行有没有错误」
- 「总结一下最近日志里的报错」
- 「分析 logs 下面的错误情况」
- 「最近系统老出问题,帮我看看日志」
---
## 二、总体流程
1. 调用脚本 `scripts/log_summary.py`,从项目根目录执行。
2. 读取输出并用自然语言向用户转述关键发现。
3. 对明显频繁的错误类型,给出简单的排查建议。
4. 输出时保持简洁,避免粘贴大段原始日志。
---
## 三、脚本调用规范
从项目根目录(包含 `start_dashboard.py` 的目录)执行命令:
```bash
python .claude/skills/log-summary/scripts/log_summary.py
```
脚本行为约定:
- 自动遍历 `logs/` 目录下所有子目录(例如 `logs/2026-02-10_23-51-10/dashboard.log`)。
- 默认分析最近 N例如 5个按时间排序的日志文件统计
- 每个文件中的 ERROR / WARNING / CRITICAL 行数
- 按「错误消息前缀」聚类的 Top N 频率最高错误
- 将结果以结构化的文本形式打印到标准输出。
你需要:
1. 运行脚本并捕获输出;
2. 读懂其中的统计数据与 Top 错误信息;
3. 用 38 句中文自然语言,对用户进行总结说明。
---
## 四、对用户的输出规范
当成功执行 `log-summary` 时,你应该向用户返回类似结构的信息:
1. **总体健康度**(一句话)
- 例如:「最近 3 次启动中共记录 2 条 ERROR、5 条 WARNING整体较为稳定。」
2. **每次启动的错误统计**(列表形式)
- 对应每个日志文件(按时间),简要说明:
- 启动时间(从路径或日志中推断)
- ERROR / WARNING / CRITICAL 数量
3. **Top 错误类型**
- 例如:「最频繁的错误是 `No module named 'src.config.config'`,共出现 4 次。」
4. **简单建议(可选)**
- 对明显重复的错误给出 13 条排查/优化建议。
避免:
- 直接原样复制整段日志;
- 输出过长的技术细节堆栈,优先摘要。
---
## 五、反模式与边界
- 如果 `logs/` 目录不存在或没有任何日志文件:
- 明确告诉用户当前没有可分析的日志,而不是编造结果。
- 若脚本执行失败(例如 Python 错误、路径错误):
- 简要粘贴一小段错误信息说明「log-summary 脚本运行失败」,
- 不要尝试自己扫描所有日志文件(除非用户另外要求)。
- 不要擅自删除或修改日志文件。

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
简单日志汇总脚本
遍历 logs/ 目录下最近的若干个 dashboard.log 文件,统计 ERROR / WARNING / CRITICAL
并输出简要汇总信息,供 log-summary Skill 调用。
"""
import os
import re
from pathlib import Path
from typing import List, Tuple
LOG_ROOT = Path("logs")
LOG_FILENAME = "dashboard.log"
MAX_FILES = 5 # 最多分析最近 N 个日志文件
LEVEL_PATTERNS = {
"ERROR": re.compile(r"\bERROR\b"),
"WARNING": re.compile(r"\bWARNING\b"),
"CRITICAL": re.compile(r"\bCRITICAL\b"),
}
def find_log_files() -> List[Path]:
if not LOG_ROOT.exists():
return []
candidates: List[Tuple[float, Path]] = []
for root, dirs, files in os.walk(LOG_ROOT):
if LOG_FILENAME in files:
p = Path(root) / LOG_FILENAME
try:
mtime = p.stat().st_mtime
except OSError:
continue
candidates.append((mtime, p))
# 按修改时间从新到旧排序
candidates.sort(key=lambda x: x[0], reverse=True)
return [p for _, p in candidates[:MAX_FILES]]
def summarize_file(path: Path):
counts = {level: 0 for level in LEVEL_PATTERNS.keys()}
top_messages = {}
try:
with path.open("r", encoding="utf-8", errors="ignore") as f:
for line in f:
for level, pattern in LEVEL_PATTERNS.items():
if pattern.search(line):
counts[level] += 1
# 取日志消息部分做前缀(粗略)
msg = line.strip()
# 截断以防过长
msg = msg[:200]
top_messages[msg] = top_messages.get(msg, 0) + 1
break
except OSError as e:
print(f"[!] 读取日志失败 {path}: {e}")
return None
# 取 Top 5
top_list = sorted(top_messages.items(), key=lambda x: x[1], reverse=True)[:5]
return counts, top_list
def main():
log_files = find_log_files()
if not log_files:
print("未找到任何日志文件logs/*/dashboard.log")
return
print(f"共找到 {len(log_files)} 个最近的日志文件(最多 {MAX_FILES} 个):\n")
overall = {level: 0 for level in LEVEL_PATTERNS.keys()}
for idx, path in enumerate(log_files, start=1):
print(f"[{idx}] 日志文件: {path}")
result = summarize_file(path)
if result is None:
print(" 无法读取该日志文件。\n")
continue
counts, top_list = result
for level, c in counts.items():
overall[level] += c
print(
" 级别统计: "
+ ", ".join(f"{lvl}={counts[lvl]}" for lvl in LEVEL_PATTERNS.keys())
)
if top_list:
print(" Top 错误/警告消息:")
for msg, n in top_list:
print(f" [{n}次] {msg}")
else:
print(" 未发现 ERROR/WARNING/CRITICAL 级别日志。")
print()
print("总体统计:")
print(
" "
+ ", ".join(f"{lvl}={overall[lvl]}" for lvl in LEVEL_PATTERNS.keys())
)
if __name__ == "__main__":
main()

View File

@@ -1,88 +0,0 @@
# 架构演进任务清单
## 概述
基于两轮架构审查发现的结构性问题,按优先级排列的演进任务。每个任务独立可交付,不依赖其他任务的完成。
## Tasks
- [-] 1. 引入 Repository 层,分离数据访问逻辑
- [x] 1.1 创建 `src/repositories/` 目录,为核心模型创建 Repository 类
- WorkOrderRepository: 封装工单的 CRUD + 按 tenant_id 过滤
- KnowledgeRepository: 封装知识库的 CRUD + 按 tenant_id 过滤
- ConversationRepository: 封装对话/会话的 CRUD + 按 tenant_id 过滤
- AlertRepository: 封装预警的 CRUD + 按 tenant_id 过滤
- [ ] 1.2 将 blueprint 中的直接 DB 查询迁移到 Repository
- workorders.py 的 get_workorders、create_workorder、delete_workorder
- knowledge.py 的 get_knowledge、add_knowledge、delete_knowledge
- conversations.py 的所有端点
- alerts.py 的所有端点
- [x] 1.3 在 Repository 基类中统一添加 tenant_id 过滤
- 所有查询方法自动附加 tenant_id 条件
- 写操作自动设置 tenant_id
- [ ] 2. 统一 LLM 客户端
- [ ] 2.1 将 `src/agent/llm_client.py` 的异步能力合并到 `src/core/llm_client.py`
- LLMClient 同时支持同步和异步调用
- 统一超时、重试、token 统计逻辑
- [ ] 2.2 让 agent_assistant.py 使用统一的 LLMClient
- 删除 `src/agent/llm_client.py` 中的 LLMManager/OpenAIClient 等重复类
- [ ] 2.3 统一 LLM 配置入口
- 所有 LLM 调用从 unified_config 读取配置
- [ ] 3. 引入 MessagePipeline 统一消息处理
- [ ] 3.1 创建 `src/dialogue/message_pipeline.py`
- 定义统一的消息处理流程:接收 → 租户解析 → 会话管理 → 知识搜索 → LLM 调用 → 保存 → 回复
- 各入口WebSocket、HTTP、飞书 bot、飞书长连接只负责协议适配
- [ ] 3.2 重构 realtime_chat.py 使用 Pipeline
- process_message 和 process_message_stream 委托给 Pipeline
- [ ] 3.3 重构飞书 bot/longconn 使用 Pipeline
- 消除 feishu_bot.py 和 feishu_longconn_service.py 中的重复逻辑
- [ ] 4. 引入 Alembic 数据库迁移
- [ ] 4.1 初始化 Alembic 配置
- alembic init, 配置 env.py 连接 unified_config
- [ ] 4.2 生成初始迁移脚本
- 从当前 models.py 生成 baseline migration
- [ ] 4.3 移除 database.py 中的 _run_migrations 手动迁移逻辑
- 改为启动时运行 alembic upgrade head
- [ ] 5. 统一配置管理
- [ ] 5.1 定义配置优先级:环境变量 > system_settings.json > 代码默认值
- [ ] 5.2 创建 ConfigService 统一读写接口
- get(key, default) / set(key, value) / get_section(section)
- 底层自动合并三个来源
- [ ] 5.3 迁移 SystemOptimizer、PerformanceConfig 使用 ConfigService
- [ ] 6. API 契约定义
- [ ] 6.1 引入 Flask-RESTX 或 apispec 生成 OpenAPI 文档
- [ ] 6.2 为所有 blueprint 端点添加 schema 定义
- [ ] 6.3 统一所有端点使用 api_response() 标准格式
- [ ] 7. 会话状态迁移到 Redis
- [ ] 7.1 将 RealtimeChatManager.active_sessions 迁移到 Redis Hash
- [ ] 7.2 将消息去重从内存缓存迁移到 Redis SET支持多进程
- [ ] 7.3 支持多实例部署(无状态 Flask + 共享 Redis
- [ ] 8. 密码哈希升级
- [ ] 8.1 将 SHA-256 替换为 bcryptpip install bcrypt
- [ ] 8.2 兼容旧密码:登录时检测旧格式,自动升级为 bcrypt
- [ ] 9. 前端状态管理优化
- [ ] 9.1 引入简易事件总线EventEmitter 模式)
- 模块间通过事件通信,不直接读写共享状态
- [ ] 9.2 将 this.xxxCurrentTenantId 等状态封装为 Store 对象
- [x] 10. 清理旧代码
- [x] 10.1 删除 src/web/static/js/core/ 目录(旧的未完成重构)
- [x] 10.2 删除 src/web/static/js/services/ 目录
- [x] 10.3 删除 src/web/static/js/components/ 目录
- [x] 10.4 删除 src/web/static/js/pages/ 目录
- [x] 10.5 清理 index.html、chat.html、chat_http.html 中对已删除 JS 的引用
## Notes
- 每个任务独立可交付,按 1 → 2 → 3 的顺序做收益最大
- 任务 4Alembic可以随时做不依赖其他任务
- 任务 7Redis 会话)只在需要多实例部署时才有必要
- 任务 8密码升级安全性高但影响面小可以穿插做

View File

@@ -1 +0,0 @@
{"specId": "b7e3c1a2-5f84-4d9e-a1b3-8c6d2e4f7a90", "workflowType": "requirements-first", "specType": "feature"}

View File

@@ -1,319 +0,0 @@
# Design Document: 对话历史租户分组展示 (conversation-tenant-view)
## Overview
本设计将对话历史页面从扁平会话列表改造为两层结构:第一层按 `tenant_id` 分组展示租户汇总卡片(会话总数、消息总数、活跃会话数、最近活跃时间),第二层展示某租户下的具体会话列表。点击会话仍可查看消息详情(保留现有第三层功能)。改造涉及三个层面:
1. **后端 API 层** — 在 `conversations_bp` 中新增租户汇总端点 `GET /api/conversations/tenants`,并为现有 `/api/conversations/sessions``/api/conversations/analytics` 端点增加 `tenant_id` 查询参数支持。
2. **业务逻辑层** — 在 `ConversationHistoryManager` 中新增 `get_tenant_summary()` 方法,并为 `get_sessions_paginated()``get_conversation_analytics()` 方法增加 `tenant_id` 过滤参数。
3. **前端展示层** — 在 `dashboard.js` 中实现 `Tenant_List_View``Tenant_Detail_View` 两个视图状态的切换逻辑,包括面包屑导航、统计面板上下文切换、搜索范围限定。
数据模型 `ChatSession``Conversation` 已有 `tenant_id` 字段(`String(50)`, indexed无需数据库迁移。
交互模式与知识库租户分组视图knowledge-tenant-view保持一致。
## Architecture
```mermaid
graph TD
subgraph Frontend["前端 (dashboard.js)"]
TLV[Tenant_List_View<br/>租户卡片列表]
TDV[Tenant_Detail_View<br/>租户会话列表]
MDV[Message_Detail_View<br/>会话消息详情]
Stats[统计面板<br/>全局/租户统计切换]
Breadcrumb[面包屑导航]
end
subgraph API["Flask Blueprint (conversations_bp)"]
EP1["GET /api/conversations/tenants"]
EP2["GET /api/conversations/sessions?tenant_id=X"]
EP3["GET /api/conversations/analytics?tenant_id=X"]
EP4["GET /api/conversations/sessions/&lt;id&gt;"]
EP5["DELETE /api/conversations/sessions/&lt;id&gt;"]
end
subgraph Service["ConversationHistoryManager"]
M1[get_tenant_summary]
M2[get_sessions_paginated<br/>+tenant_id filter]
M3[get_conversation_analytics<br/>+tenant_id filter]
M4[get_session_messages]
M5[delete_session]
end
subgraph DB["SQLAlchemy"]
CS[ChatSession<br/>tenant_id indexed]
CV[Conversation<br/>tenant_id indexed]
end
TLV -->|点击租户卡片| TDV
TDV -->|点击会话行| MDV
TDV -->|面包屑返回| TLV
MDV -->|面包屑返回| TDV
TLV --> EP1
TDV --> EP2
Stats --> EP3
MDV --> EP4
TDV --> EP5
EP1 --> M1
EP2 --> M2
EP3 --> M3
EP4 --> M4
EP5 --> M5
M1 --> CS
M2 --> CS
M3 --> CS & CV
M4 --> CV
M5 --> CS & CV
```
### 设计决策
- **不引入新模型/表**`tenant_id` 已存在于 `ChatSession``Conversation`,聚合查询通过 `GROUP BY` 实现,无需额外的 Tenant 表。
- **视图状态管理在前端**:使用 JS 变量 `conversationCurrentTenantId` 控制当前视图层级,避免引入前端路由框架。与 knowledge-tenant-view 的 `currentTenantId` 模式一致。
- **统计面板复用**:同一个统计面板根据 `conversationCurrentTenantId` 是否为 `null` 决定请求全局或租户级统计。
- **搜索范围自动限定**:当处于 `Tenant_Detail_View` 时,搜索请求自动附加 `tenant_id` 参数。
- **复用现有删除逻辑**`delete_session()` 已实现删除会话及关联消息,无需修改。
## Components and Interfaces
### 1. ConversationHistoryManager 新增/修改方法
```python
# 新增方法
def get_tenant_summary(self) -> List[Dict[str, Any]]:
"""
按 tenant_id 聚合 ChatSession返回租户汇总列表。
返回格式: [
{
"tenant_id": "market_a",
"session_count": 15,
"message_count": 230,
"active_session_count": 5,
"last_active_time": "2026-03-20T10:30:00"
}, ...
]
按 last_active_time 降序排列。
"""
# 修改方法签名
def get_sessions_paginated(
self,
page: int = 1,
per_page: int = 20,
status: Optional[str] = None,
search: str = '',
date_filter: str = '',
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
def get_conversation_analytics(
self,
work_order_id: Optional[int] = None,
days: int = 7,
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
```
### 2. Conversations API 新增/修改端点
| 端点 | 方法 | 变更 | 说明 |
|------|------|------|------|
| `/api/conversations/tenants` | GET | 新增 | 返回租户汇总数组 |
| `/api/conversations/sessions` | GET | 修改 | 增加 `tenant_id` 查询参数 |
| `/api/conversations/analytics` | GET | 修改 | 增加 `tenant_id` 查询参数 |
现有端点保持不变:
- `GET /api/conversations/sessions/<session_id>` — 获取会话消息详情
- `DELETE /api/conversations/sessions/<session_id>` — 删除会话
### 3. 前端组件
| 组件/函数 | 职责 |
|-----------|------|
| `loadConversationTenantList()` | 请求 `/api/conversations/tenants`,渲染租户卡片 |
| `loadConversationTenantDetail(tenantId, page)` | 请求 `/api/conversations/sessions?tenant_id=X`,渲染会话列表 |
| `renderConversationBreadcrumb(tenantId, sessionTitle)` | 渲染面包屑 "对话历史 > {tenant_id}" 或 "对话历史 > {tenant_id} > {session_title}" |
| `loadConversationStats(tenantId)` | 根据 tenantId 是否为 null 请求全局/租户统计 |
| `searchConversationSessions()` | 搜索时自动附加 `conversationCurrentTenantId` |
## Data Models
### ChatSession现有无变更
```python
class ChatSession(Base):
__tablename__ = "chat_sessions"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), nullable=False, default="default", index=True)
session_id = Column(String(100), unique=True, nullable=False)
user_id = Column(String(100), nullable=True)
work_order_id = Column(Integer, ForeignKey("work_orders.id"), nullable=True)
title = Column(String(200), nullable=True)
status = Column(String(20), default="active") # active, ended
message_count = Column(Integer, default=0)
source = Column(String(50), nullable=True)
ip_address = Column(String(45), nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
ended_at = Column(DateTime, nullable=True)
```
### Conversation现有无变更
```python
class Conversation(Base):
__tablename__ = "conversations"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), nullable=False, default="default", index=True)
session_id = Column(String(100), ForeignKey("chat_sessions.session_id"), nullable=True)
work_order_id = Column(Integer, ForeignKey("work_orders.id"))
user_message = Column(Text, nullable=False)
assistant_response = Column(Text, nullable=False)
timestamp = Column(DateTime, default=datetime.now)
confidence_score = Column(Float)
response_time = Column(Float)
# ... 其他字段
```
### Tenant SummaryAPI 响应结构,非持久化)
```json
{
"tenant_id": "market_a",
"session_count": 15,
"message_count": 230,
"active_session_count": 5,
"last_active_time": "2026-03-20T10:30:00"
}
```
### Analytics 响应结构(扩展)
现有 analytics 响应增加 `tenant_id` 字段(仅当按租户筛选时返回),其余结构不变。
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Tenant summary aggregation correctness
*For any* set of `ChatSession` records with mixed `tenant_id`, `status`, and `message_count` values, calling `get_tenant_summary()` should return a list where each element's `session_count` equals the number of `ChatSession` records for that `tenant_id`, each `message_count` equals the sum of `message_count` fields for that `tenant_id`, and each `active_session_count` equals the count of `ChatSession` records with `status == 'active'` for that `tenant_id`.
**Validates: Requirements 1.1, 1.2, 1.3, 1.4**
### Property 2: Tenant summary sorted by last_active_time descending
*For any* result returned by `get_tenant_summary()`, the list should be sorted such that for every consecutive pair of elements `(a, b)`, `a.last_active_time >= b.last_active_time`.
**Validates: Requirements 1.5**
### Property 3: Session filtering by tenant, status, and search
*For any* combination of `tenant_id`, `status`, and `search` parameters, all sessions returned by `get_sessions_paginated()` should satisfy all specified filter conditions simultaneously. Specifically: every returned session's `tenant_id` matches the requested `tenant_id`, every returned session's `status` matches the `status` filter (if provided), and every returned session's `title` or `session_id` contains the `search` string (if provided).
**Validates: Requirements 2.1, 2.3**
### Property 4: Pagination consistency with tenant filter
*For any* `tenant_id` and valid `page`/`per_page` values, the sessions returned by `get_sessions_paginated(tenant_id=X, page=P, per_page=N)` should be a correct slice of the full filtered result set. The `total` field should equal the count of all matching sessions, `total_pages` should equal `ceil(total / per_page)`, and the number of returned sessions should equal `min(per_page, total - (page-1)*per_page)` when `page <= total_pages`.
**Validates: Requirements 2.2**
### Property 5: Session deletion removes session and all associated messages
*For any* `ChatSession` and its associated `Conversation` records, after calling `delete_session(session_id)`, querying for the `ChatSession` by `session_id` should return no results, and querying for `Conversation` records with that `session_id` should also return no results.
**Validates: Requirements 6.2**
### Property 6: Search results scoped to tenant
*For any* search query and `tenant_id`, all sessions returned by `get_sessions_paginated(search=Q, tenant_id=X)` should have `tenant_id == X`. The result set should be a subset of what `get_sessions_paginated(search=Q)` returns (without tenant filter).
**Validates: Requirements 7.1, 7.2**
### Property 7: Analytics scoped to tenant
*For any* `tenant_id`, the analytics returned by `get_conversation_analytics(tenant_id=X)` should reflect only `ChatSession` and `Conversation` records with `tenant_id == X`. When `tenant_id` is omitted, the analytics should aggregate across all tenants. Specifically, the conversation total count with a tenant filter should be less than or equal to the global total count.
**Validates: Requirements 8.3, 8.4**
## Error Handling
### API 层错误处理
所有 API 端点使用 try/except 包裹,捕获异常后返回统一错误格式:
| 异常场景 | HTTP 状态码 | 响应格式 |
|----------|------------|---------|
| 参数校验失败(如 `page < 1` | 400 | `{"error": "描述信息"}` |
| 数据库查询异常 | 500 | `{"error": "描述信息"}` |
| 正常但无数据 | 200 | 空数组 `[]``{"sessions": [], "total": 0}` |
### 业务逻辑层错误处理
- `get_tenant_summary()` — 数据库异常时返回空列表 `[]`,记录 error 日志。
- `get_sessions_paginated()` — 异常时返回空结构 `{"sessions": [], "total": 0, ...}`(现有行为保持不变)。
- `get_conversation_analytics()` — 异常时返回空字典 `{}`(现有行为保持不变)。
- `delete_session()` — 异常时返回 `False`,记录 error 日志(现有行为保持不变)。
### 前端错误处理
- API 请求失败时通过 `showNotification(message, 'error')` 展示错误提示。
- 网络超时或断连时显示通用错误消息。
- 删除操作失败时显示具体失败原因。
## Testing Strategy
### 测试框架
- **单元测试**: `pytest`
- **属性测试**: `hypothesis`Python property-based testing 库)
- **每个属性测试最少运行 100 次迭代**
### 属性测试Property-Based Tests
每个 Correctness Property 对应一个属性测试,使用 `hypothesis``@given` 装饰器生成随机输入。
测试标签格式: `Feature: conversation-tenant-view, Property {number}: {property_text}`
| Property | 测试描述 | 生成策略 |
|----------|---------|---------|
| Property 1 | 生成随机 ChatSession 列表(混合 tenant_id、status、message_count验证 `get_tenant_summary()` 聚合正确性 | `st.lists(st.builds(ChatSession, tenant_id=st.sampled_from([...]), status=st.sampled_from(['active','ended']), message_count=st.integers(min_value=0, max_value=100)))` |
| Property 2 | 验证 `get_tenant_summary()` 返回列表按 last_active_time 降序 | 复用 Property 1 的生成策略 |
| Property 3 | 生成随机 tenant_id + status + search 组合,验证过滤结果一致性 | `st.sampled_from(tenant_ids)`, `st.sampled_from(['active','ended',''])`, `st.text(min_size=0, max_size=20)` |
| Property 4 | 生成随机 page/per_page验证分页切片正确性 | `st.integers(min_value=1, max_value=10)` for page/per_page |
| Property 5 | 创建随机会话及关联消息,删除后验证两者均不存在 | `st.text(min_size=1, max_size=50)` for session_id, `st.integers(min_value=1, max_value=10)` for message count |
| Property 6 | 生成随机搜索词和 tenant_id验证搜索结果范围 | `st.text()` for query, `st.sampled_from(tenant_ids)` |
| Property 7 | 生成随机 tenant_id验证 analytics 数据与手动聚合一致 | `st.sampled_from(tenant_ids)` + `st.none()` |
### 单元测试Unit Tests
单元测试聚焦于边界情况和具体示例:
- **边界**: 无 ChatSession 记录时 `get_tenant_summary()` 返回空数组
- **边界**: 不存在的 `tenant_id` 查询返回空列表 + `total=0`
- **示例**: 数据库异常时 API 返回 500
- **示例**: 删除最后一个会话后租户从汇总中消失
- **集成**: 前端 `loadConversationTenantList()` → API → Manager 完整链路
### 测试配置
```python
from hypothesis import settings
@settings(max_examples=100)
```
每个属性测试函数头部添加注释引用设计文档中的 Property 编号,例如:
```python
# Feature: conversation-tenant-view, Property 1: Tenant summary aggregation correctness
@given(sessions=st.lists(chat_session_strategy(), min_size=0, max_size=50))
def test_tenant_summary_aggregation(sessions):
...
```

View File

@@ -1,116 +0,0 @@
# Requirements Document
## Introduction
对话历史租户分组展示功能。当前对话历史页面以扁平的会话列表展示所有 `ChatSession` 记录,缺乏租户(市场)维度的组织结构。本功能将对话历史页面改造为两层结构:第一层按租户分组展示汇总信息(会话总数、消息总数、最近活跃时间等),第二层展示某个租户下的具体会话列表。点击具体会话仍可查看消息详情(保留现有功能)。交互模式与知识库租户分组视图保持一致,包括卡片视图、面包屑导航、搜索范围限定和统计面板上下文切换。
## Glossary
- **Dashboard**: Flask + Jinja2 + Bootstrap 5 构建的 Web 管理后台主页面(`dashboard.html`
- **Conversation_Tab**: Dashboard 中 `#conversation-history-tab` 区域,用于展示和管理对话历史
- **Conversation_API**: Flask Blueprint `conversations_bp`,提供对话相关的 REST API`/api/conversations/*`
- **History_Manager**: `ConversationHistoryManager` 类,封装对话历史的数据库查询与业务逻辑
- **Tenant**: 租户,即市场标识(如 `market_a``market_b`),通过 `ChatSession.tenant_id` 字段区分
- **Tenant_Summary**: 租户汇总信息,包含租户 ID、会话总数、消息总数、活跃会话数、最近活跃时间等聚合数据
- **Tenant_List_View**: 第一层视图,以卡片形式展示所有租户的对话汇总信息
- **Tenant_Detail_View**: 第二层视图,展示某个租户下的具体会话列表(含分页、筛选)
- **ChatSession**: SQLAlchemy 数据模型,包含 `tenant_id``session_id``title``status``message_count``source``created_at``updated_at` 等字段
- **Conversation**: SQLAlchemy 数据模型,表示单条对话消息,包含 `tenant_id``session_id``user_message``assistant_response` 等字段
## Requirements
### Requirement 1: 租户汇总 API
**User Story:** 作为管理员,我希望后端提供按租户分组的对话会话汇总接口,以便前端展示每个租户的对话统计。
#### Acceptance Criteria
1. WHEN a GET request is sent to `/api/conversations/tenants`, THE Conversation_API SHALL return a JSON array of Tenant_Summary objects, each containing `tenant_id`, `session_count`, `message_count`, `active_session_count`, and `last_active_time`
2. THE Conversation_API SHALL compute `session_count` by counting all ChatSession records for each Tenant
3. THE Conversation_API SHALL compute `message_count` by summing the `message_count` field of all ChatSession records for each Tenant
4. THE Conversation_API SHALL compute `active_session_count` by counting ChatSession records with `status == 'active'` for each Tenant
5. THE Conversation_API SHALL sort the Tenant_Summary array by `last_active_time` in descending order
6. WHEN no ChatSession records exist, THE Conversation_API SHALL return an empty JSON array with HTTP status 200
7. IF a database query error occurs, THEN THE Conversation_API SHALL return an error response with HTTP status 500 and a descriptive error message
### Requirement 2: 租户会话列表 API
**User Story:** 作为管理员,我希望后端提供按租户筛选的会话分页接口,以便在点击某个租户后查看该租户下的具体会话列表。
#### Acceptance Criteria
1. WHEN a GET request with query parameter `tenant_id` is sent to `/api/conversations/sessions`, THE Conversation_API SHALL return only the ChatSession records belonging to the specified Tenant
2. THE Conversation_API SHALL support pagination via `page` and `per_page` query parameters when filtering by `tenant_id`
3. THE Conversation_API SHALL support `status` and `search` query parameters for further filtering within a Tenant
4. WHEN the `tenant_id` parameter value does not match any existing ChatSession records, THE Conversation_API SHALL return an empty session list with `total` equal to 0 and HTTP status 200
5. THE History_Manager SHALL accept `tenant_id` as a filter parameter in the `get_sessions_paginated` method and return paginated results scoped to the specified Tenant
### Requirement 3: 租户列表视图(第一层)
**User Story:** 作为管理员,我希望对话历史页面首先展示按租户分组的汇总卡片,以便快速了解各市场的对话活跃度。
#### Acceptance Criteria
1. WHEN the Conversation_Tab is activated, THE Dashboard SHALL display a Tenant_List_View showing one card per Tenant
2. THE Tenant_List_View SHALL display the following information for each Tenant: tenant_id租户名称, session_count会话总数, message_count消息总数, active_session_count活跃会话数, last_active_time最近活跃时间
3. WHEN the Tenant_List_View is loading data, THE Dashboard SHALL display a loading spinner in the Conversation_Tab area
4. WHEN no tenants exist, THE Dashboard SHALL display a placeholder message indicating that no conversation sessions are available
5. THE Tenant_List_View SHALL refresh its data when the user clicks a refresh button
### Requirement 4: 租户详情视图(第二层)
**User Story:** 作为管理员,我希望点击某个租户卡片后能查看该租户下的具体会话列表,以便管理和审查对话内容。
#### Acceptance Criteria
1. WHEN a user clicks on a Tenant card in the Tenant_List_View, THE Dashboard SHALL transition to the Tenant_Detail_View showing ChatSession records for the selected Tenant
2. THE Tenant_Detail_View SHALL display each ChatSession with the following fields: title会话标题, message_count消息数, status状态, source来源, created_at创建时间, updated_at最近更新时间
3. THE Tenant_Detail_View SHALL provide a breadcrumb navigation showing "对话历史 > {tenant_id}" to indicate the current context
4. WHEN the user clicks the breadcrumb "对话历史" link, THE Dashboard SHALL navigate back to the Tenant_List_View
5. THE Tenant_Detail_View SHALL support pagination with configurable page size
6. THE Tenant_Detail_View SHALL support filtering by session status and date range
### Requirement 5: 会话详情查看(第三层保留)
**User Story:** 作为管理员,我希望在租户详情视图中点击某个会话后能查看该会话的消息详情,以便审查具体对话内容。
#### Acceptance Criteria
1. WHEN a user clicks on a ChatSession row in the Tenant_Detail_View, THE Dashboard SHALL display the message detail view showing all Conversation records for the selected ChatSession
2. THE Dashboard SHALL retain the existing message detail display logic and UI layout
3. THE Dashboard SHALL provide a breadcrumb navigation showing "对话历史 > {tenant_id} > {session_title}" in the message detail view
4. WHEN the user clicks the breadcrumb "{tenant_id}" link, THE Dashboard SHALL navigate back to the Tenant_Detail_View for the corresponding Tenant
### Requirement 6: 会话管理操作
**User Story:** 作为管理员,我希望在租户详情视图中能对会话执行删除操作,以便维护对话历史数据。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL provide a delete button for each ChatSession row
2. WHEN a user deletes a ChatSession in the Tenant_Detail_View, THE Conversation_API SHALL delete the ChatSession and all associated Conversation records
3. WHEN a user deletes a ChatSession, THE Dashboard SHALL refresh the Tenant_Detail_View to reflect the updated data
4. WHEN a user deletes all ChatSession records for a Tenant, THE Dashboard SHALL navigate back to the Tenant_List_View and remove the empty Tenant card
5. IF a ChatSession deletion fails, THEN THE Dashboard SHALL display an error notification with the failure reason
### Requirement 7: 搜索功能适配
**User Story:** 作为管理员,我希望在租户详情视图中搜索会话时,搜索范围限定在当前租户内,以便精确查找。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL scope the session search to the currently selected Tenant
2. WHEN a search query is submitted in the Tenant_Detail_View, THE Conversation_API SHALL filter search results by the specified `tenant_id`
3. WHEN the search query is cleared, THE Dashboard SHALL restore the full paginated session list for the current Tenant
4. THE History_Manager search method SHALL accept an optional `tenant_id` parameter to limit search scope
### Requirement 8: 统计信息适配
**User Story:** 作为管理员,我希望对话历史统计面板在租户列表视图时展示全局统计,在租户详情视图时展示当前租户的统计,以便获取准确的上下文信息。
#### Acceptance Criteria
1. WHILE the Tenant_List_View is displayed, THE Dashboard SHALL show global conversation statistics (total sessions across all tenants, total messages, total active sessions)
2. WHILE the Tenant_Detail_View is displayed, THE Dashboard SHALL show statistics scoped to the selected Tenant
3. WHEN a GET request with query parameter `tenant_id` is sent to `/api/conversations/analytics`, THE Conversation_API SHALL return analytics data filtered by the specified Tenant
4. WHEN the `tenant_id` parameter is omitted from the analytics request, THE Conversation_API SHALL return global analytics across all tenants

View File

@@ -1,142 +0,0 @@
# Implementation Plan: 对话历史租户分组展示 (conversation-tenant-view)
## Overview
将对话历史页面从扁平会话列表改造为两层结构:第一层按 `tenant_id` 分组展示租户汇总卡片,第二层展示租户下的会话列表。改造涉及 ConversationHistoryManager 业务逻辑层、Flask API 层、前端 dashboard.js 三个层面。交互模式与知识库租户分组视图保持一致。
## Tasks
- [x] 1. ConversationHistoryManager 新增 get_tenant_summary 方法
- [x] 1.1 在 `src/dialogue/conversation_history.py` 中新增 `get_tenant_summary()` 方法
- 使用 SQLAlchemy `GROUP BY ChatSession.tenant_id` 聚合所有 ChatSession 记录
- 计算每个租户的 `session_count`(会话总数)、`message_count`消息总数sum of message_count`active_session_count`status=='active' 的会话数)、`last_active_time`max of updated_at
-`last_active_time` 降序排列
- 数据库异常时返回空列表 `[]`,记录 error 日志
- 无 ChatSession 记录时返回空列表 `[]`
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [ ]* 1.2 为 get_tenant_summary 编写属性测试
- **Property 1: Tenant summary aggregation correctness**
- **Property 2: Tenant summary sorted by last_active_time descending**
- 使用 `hypothesis` 生成随机 ChatSession 列表(混合 tenant_id、status、message_count验证聚合正确性和排序
- **Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5**
- [x] 2. ConversationHistoryManager 现有方法增加 tenant_id 过滤
- [x] 2.1 为 `get_sessions_paginated()` 增加 `tenant_id` 可选参数
-`src/dialogue/conversation_history.py` 中修改方法签名,增加 `tenant_id: Optional[str] = None`
-`tenant_id` 不为 None 时,在查询中增加 `ChatSession.tenant_id == tenant_id` 过滤条件
- 返回结构不变,仅过滤范围缩小
- _Requirements: 2.1, 2.2, 2.3, 2.5_
- [ ]* 2.2 为 get_sessions_paginated 的 tenant_id 过滤编写属性测试
- **Property 3: Session filtering by tenant, status, and search**
- **Property 4: Pagination consistency with tenant filter**
- **Validates: Requirements 2.1, 2.2, 2.3**
- [x] 2.3 为 `get_conversation_analytics()` 增加 `tenant_id` 可选参数
-`tenant_id` 不为 None 时,所有统计查询增加 `ChatSession.tenant_id == tenant_id``Conversation.tenant_id == tenant_id` 过滤
- 返回结构不变
- _Requirements: 8.3, 8.4_
- [ ]* 2.4 为 get_conversation_analytics 的 tenant_id 过滤编写属性测试
- **Property 7: Analytics scoped to tenant**
- **Validates: Requirements 8.3, 8.4**
- [x] 3. Checkpoint - 确保后端业务逻辑层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Conversations API 层新增和修改端点
- [x] 4.1 在 `src/web/blueprints/conversations.py` 中新增 `GET /api/conversations/tenants` 端点
- 调用 `history_manager.get_tenant_summary()` 返回租户汇总 JSON 数组
- 使用 try/except 包裹,异常时返回 HTTP 500
- _Requirements: 1.1, 1.5, 1.6, 1.7_
- [x] 4.2 修改 `GET /api/conversations/sessions` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `history_manager.get_sessions_paginated()`
- _Requirements: 2.1, 2.2, 2.3, 2.4_
- [x] 4.3 修改 `GET /api/conversations/analytics` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `history_manager.get_conversation_analytics()`
- _Requirements: 8.3, 8.4_
- [ ]* 4.4 为新增和修改的 API 端点编写单元测试
- 测试 `/api/conversations/tenants` 返回正确的汇总数据
- 测试各端点的 `tenant_id` 参数过滤行为
- 测试空数据和异常情况
- _Requirements: 1.1, 1.6, 1.7, 2.4_
- [x] 5. Checkpoint - 确保后端 API 层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. 前端 Tenant_List_View租户列表视图
- [x] 6.1 在 `src/web/static/js/dashboard.js` 中实现 `loadConversationTenantList()` 函数
- 请求 `GET /api/conversations/tenants` 获取租户汇总数据
- 渲染租户卡片列表,每张卡片展示 `tenant_id``session_count``message_count``active_session_count``last_active_time`
- 添加加载中 spinner 状态
- 无租户时展示空状态占位提示
- 卡片点击事件绑定,调用 `loadConversationTenantDetail(tenantId)`
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [x] 6.2 实现刷新按钮功能
- 在对话历史 tab 区域添加刷新按钮,点击时重新调用 `loadConversationTenantList()`
- _Requirements: 3.5_
- [x] 7. 前端 Tenant_Detail_View租户详情视图
- [x] 7.1 实现 `loadConversationTenantDetail(tenantId, page)` 函数
- 请求 `GET /api/conversations/sessions?tenant_id=X&page=P&per_page=N` 获取会话列表
- 渲染会话表格,展示 title、message_count、status、source、created_at、updated_at
- 实现分页控件
- 支持 status 和 date_filter 筛选
- _Requirements: 4.1, 4.2, 4.5, 4.6_
- [x] 7.2 实现面包屑导航 `renderConversationBreadcrumb(tenantId, sessionTitle)`
- 展示 "对话历史 > {tenant_id}" 面包屑(租户详情视图)
- 展示 "对话历史 > {tenant_id} > {session_title}" 面包屑(消息详情视图)
- 点击 "对话历史" 链接时调用 `loadConversationTenantList()` 返回租户列表视图
- 点击 "{tenant_id}" 链接时调用 `loadConversationTenantDetail(tenantId)` 返回租户详情视图
- 管理 `conversationCurrentTenantId` 状态变量控制视图层级
- _Requirements: 4.3, 4.4, 5.3, 5.4_
- [x] 7.3 在 Tenant_Detail_View 中集成会话管理操作
- 每行会话提供删除按钮,调用 `DELETE /api/conversations/sessions/<session_id>`
- 删除成功后刷新当前租户详情视图
- 删除所有会话后自动返回租户列表视图并移除空租户卡片
- 操作失败时通过 `showNotification` 展示错误提示
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
- [ ]* 7.4 为删除操作编写属性测试
- **Property 5: Session deletion removes session and all associated messages**
- **Validates: Requirements 6.2**
- [x] 8. 前端搜索和统计面板适配
- [x] 8.1 修改搜索功能 `searchConversationSessions()`
- 在 Tenant_Detail_View 中搜索时自动附加 `tenant_id` 参数
- 清空搜索时恢复当前租户的完整分页列表
- _Requirements: 7.1, 7.2, 7.3_
- [ ]* 8.2 为搜索范围限定编写属性测试
- **Property 6: Search results scoped to tenant**
- **Validates: Requirements 7.1, 7.2**
- [x] 8.3 修改 `loadConversationStats(tenantId)` 函数
-`conversationCurrentTenantId` 为 null 时请求全局统计
-`conversationCurrentTenantId` 有值时请求 `GET /api/conversations/analytics?tenant_id=X`
- _Requirements: 8.1, 8.2_
- [x] 9. 前端 HTML 模板更新
- [x] 9.1 在 `src/web/templates/dashboard.html``#conversation-history-tab` 区域添加必要的 DOM 容器
- 添加面包屑容器、租户卡片列表容器、租户详情容器
- 确保与现有 Bootstrap 5 样式一致,与知识库租户视图风格统一
- _Requirements: 3.1, 4.3_
- [x] 10. Final checkpoint - 确保所有功能集成完成
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- 数据模型 `ChatSession``Conversation` 已有 `tenant_id` 字段且已建索引,无需数据库迁移
- 交互模式与知识库租户分组视图 (knowledge-tenant-view) 保持一致

View File

@@ -1 +0,0 @@
{"specId": "0d6981a4-ab44-429e-966d-0874ce82383c", "workflowType": "requirements-first", "specType": "feature"}

View File

@@ -1,310 +0,0 @@
# Design Document: 知识库租户分组展示 (knowledge-tenant-view)
## Overview
本设计将知识库管理页面从扁平列表改造为两层结构:第一层按 `tenant_id` 分组展示租户汇总卡片,第二层展示某租户下的知识条目列表。改造涉及三个层面:
1. **后端 API 层** — 在 `knowledge_bp` 中新增租户汇总端点 `/api/knowledge/tenants`,并为现有 `/api/knowledge``/api/knowledge/stats` 端点增加 `tenant_id` 查询参数支持。
2. **业务逻辑层** — 在 `KnowledgeManager` 中新增 `get_tenant_summary()` 方法,并为 `get_knowledge_paginated()``search_knowledge()``get_knowledge_stats()` 方法增加 `tenant_id` 过滤参数。`add_knowledge_entry()` 方法也需接受 `tenant_id` 参数。
3. **前端展示层** — 在 `dashboard.js` 中实现 `Tenant_List_View``Tenant_Detail_View` 两个视图状态的切换逻辑,包括面包屑导航、统计面板上下文切换、搜索范围限定。
数据模型 `KnowledgeEntry` 已有 `tenant_id` 字段(`String(50)`, indexed无需数据库迁移。
## Architecture
```mermaid
graph TD
subgraph Frontend["前端 (dashboard.js)"]
TLV[Tenant_List_View<br/>租户卡片列表]
TDV[Tenant_Detail_View<br/>租户知识条目列表]
Stats[统计面板<br/>全局/租户统计切换]
Breadcrumb[面包屑导航]
end
subgraph API["Flask Blueprint (knowledge_bp)"]
EP1["GET /api/knowledge/tenants"]
EP2["GET /api/knowledge?tenant_id=X"]
EP3["GET /api/knowledge/stats?tenant_id=X"]
EP4["GET /api/knowledge/search?q=...&tenant_id=X"]
EP5["POST /api/knowledge (含 tenant_id)"]
end
subgraph Service["KnowledgeManager"]
M1[get_tenant_summary]
M2[get_knowledge_paginated<br/>+tenant_id filter]
M3[get_knowledge_stats<br/>+tenant_id filter]
M4[search_knowledge<br/>+tenant_id filter]
M5[add_knowledge_entry<br/>+tenant_id param]
end
subgraph DB["SQLAlchemy"]
KE[KnowledgeEntry<br/>tenant_id indexed]
end
TLV -->|点击租户卡片| TDV
TDV -->|面包屑返回| TLV
TLV --> EP1
TDV --> EP2
TDV --> EP4
Stats --> EP3
TDV --> EP5
EP1 --> M1
EP2 --> M2
EP3 --> M3
EP4 --> M4
EP5 --> M5
M1 --> KE
M2 --> KE
M3 --> KE
M4 --> KE
M5 --> KE
```
### 设计决策
- **不引入新模型/表**`tenant_id` 已存在于 `KnowledgeEntry`,聚合查询通过 `GROUP BY` 实现,无需额外的 Tenant 表。
- **视图状态管理在前端**:使用 JS 变量 `currentTenantId` 控制当前视图层级,避免引入前端路由框架。
- **统计面板复用**:同一个统计面板根据 `currentTenantId` 是否为 `null` 决定请求全局或租户级统计。
- **搜索范围自动限定**:当处于 `Tenant_Detail_View` 时,搜索请求自动附加 `tenant_id` 参数。
## Components and Interfaces
### 1. KnowledgeManager 新增/修改方法
```python
# 新增方法
def get_tenant_summary(self) -> List[Dict[str, Any]]:
"""
按 tenant_id 聚合活跃知识条目,返回租户汇总列表。
返回格式: [
{
"tenant_id": "market_a",
"entry_count": 42,
"verified_count": 30,
"category_distribution": {"FAQ": 20, "故障排查": 22}
}, ...
]
按 entry_count 降序排列。
"""
# 修改方法签名
def get_knowledge_paginated(
self, page=1, per_page=10,
category_filter='', verified_filter='',
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
def search_knowledge(
self, query: str, top_k=3,
verified_only=True,
tenant_id: Optional[str] = None # 新增
) -> List[Dict[str, Any]]
def get_knowledge_stats(
self,
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
def add_knowledge_entry(
self, question, answer, category,
confidence_score=0.5, is_verified=False,
tenant_id: Optional[str] = None # 新增,默认取 config
) -> bool
```
### 2. Knowledge API 新增/修改端点
| 端点 | 方法 | 变更 | 说明 |
|------|------|------|------|
| `/api/knowledge/tenants` | GET | 新增 | 返回租户汇总数组 |
| `/api/knowledge` | GET | 修改 | 增加 `tenant_id` 查询参数 |
| `/api/knowledge/stats` | GET | 修改 | 增加 `tenant_id` 查询参数 |
| `/api/knowledge/search` | GET | 修改 | 增加 `tenant_id` 查询参数 |
| `/api/knowledge` | POST | 修改 | 请求体增加 `tenant_id` 字段 |
### 3. 前端组件
| 组件 | 职责 |
|------|------|
| `loadTenantList()` | 请求 `/api/knowledge/tenants`,渲染租户卡片 |
| `loadTenantDetail(tenantId, page)` | 请求 `/api/knowledge?tenant_id=X`,渲染知识条目列表 |
| `renderBreadcrumb(tenantId)` | 渲染面包屑 "知识库 > {tenant_id}" |
| `loadKnowledgeStats(tenantId)` | 根据 tenantId 是否为 null 请求全局/租户统计 |
| `searchKnowledge()` | 搜索时自动附加 `currentTenantId` |
## Data Models
### KnowledgeEntry现有无变更
```python
class KnowledgeEntry(Base):
__tablename__ = "knowledge_entries"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), nullable=False, default="default", index=True)
question = Column(Text, nullable=False)
answer = Column(Text, nullable=False)
category = Column(String(100), nullable=False)
confidence_score = Column(Float, default=0.0)
usage_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
is_active = Column(Boolean, default=True)
is_verified = Column(Boolean, default=False)
verified_by = Column(String(100))
verified_at = Column(DateTime)
vector_embedding = Column(Text)
search_frequency = Column(Integer, default=0)
last_accessed = Column(DateTime)
relevance_score = Column(Float)
```
### Tenant SummaryAPI 响应结构,非持久化)
```json
{
"tenant_id": "market_a",
"entry_count": 42,
"verified_count": 30,
"category_distribution": {
"FAQ": 20,
"故障排查": 22
}
}
```
### Stats 响应结构(扩展)
```json
{
"total_entries": 100,
"active_entries": 80,
"category_distribution": {"FAQ": 40, "故障排查": 60},
"average_confidence": 0.85,
"tenant_id": "market_a" // 新增,仅当按租户筛选时返回
}
```
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Tenant summary correctly aggregates active entries
*For any* set of `KnowledgeEntry` records with mixed `is_active` and `tenant_id` values, calling `get_tenant_summary()` should return a list where each element's `entry_count` equals the number of active entries for that `tenant_id`, each `verified_count` equals the number of active+verified entries for that `tenant_id`, and each `category_distribution` correctly reflects the category counts of active entries for that `tenant_id`.
**Validates: Requirements 1.1, 1.2**
### Property 2: Tenant summary sorted by entry_count descending
*For any* result returned by `get_tenant_summary()`, the list should be sorted such that for every consecutive pair of elements `(a, b)`, `a.entry_count >= b.entry_count`.
**Validates: Requirements 1.3**
### Property 3: Knowledge entry filtering by tenant, category, and verified status
*For any* combination of `tenant_id`, `category_filter`, and `verified_filter` parameters, all entries returned by `get_knowledge_paginated()` should satisfy all specified filter conditions simultaneously. Specifically: every returned entry's `tenant_id` matches the requested `tenant_id`, every returned entry's `category` matches `category_filter` (if provided), and every returned entry's `is_verified` matches `verified_filter` (if provided).
**Validates: Requirements 2.1, 2.3**
### Property 4: Pagination consistency with tenant filter
*For any* `tenant_id` and valid `page`/`per_page` values, the entries returned by `get_knowledge_paginated(tenant_id=X, page=P, per_page=N)` should be a correct slice of the full filtered result set. The `total` field should equal the count of all matching entries, `total_pages` should equal `ceil(total / per_page)`, and the number of returned entries should equal `min(per_page, total - (page-1)*per_page)` when `page <= total_pages`.
**Validates: Requirements 2.2**
### Property 5: New entry tenant association
*For any* valid `tenant_id` and valid entry data (question, answer, category), calling `add_knowledge_entry(tenant_id=X, ...)` should result in the newly created `KnowledgeEntry` record having `tenant_id == X`. If `tenant_id` is not provided, it should default to the configured `get_config().server.tenant_id`.
**Validates: Requirements 5.2**
### Property 6: Search results scoped to tenant
*For any* search query and `tenant_id`, all results returned by `search_knowledge(query=Q, tenant_id=X)` should have `tenant_id == X`. The result set should be a subset of what `search_knowledge(query=Q)` returns (without tenant filter).
**Validates: Requirements 6.2**
### Property 7: Stats scoped to tenant
*For any* `tenant_id`, the statistics returned by `get_knowledge_stats(tenant_id=X)` should reflect only entries with `tenant_id == X`. Specifically, `total_entries` should equal the count of active entries for that tenant, and `average_confidence` should equal the mean confidence of those entries. When `tenant_id` is omitted, the stats should aggregate across all tenants.
**Validates: Requirements 7.3, 7.4**
## Error Handling
### API 层错误处理
所有 API 端点已使用 `@handle_api_errors` 装饰器,该装饰器捕获以下异常:
| 异常类型 | HTTP 状态码 | 说明 |
|----------|------------|------|
| `ValueError` | 400 | 参数校验失败(如 `page < 1` |
| `PermissionError` | 403 | 权限不足 |
| `Exception` | 500 | 数据库查询失败等未预期错误 |
### 业务逻辑层错误处理
- `get_tenant_summary()` — 数据库异常时返回空列表 `[]`,记录 error 日志。
- `get_knowledge_paginated()` — 异常时返回空结构 `{"knowledge": [], "total": 0, ...}`(现有行为保持不变)。
- `get_knowledge_stats()` — 异常时返回空字典 `{}`(现有行为保持不变)。
- `add_knowledge_entry()` — 异常时返回 `False`,记录 error 日志。
### 前端错误处理
- API 请求失败时通过 `showNotification(message, 'error')` 展示错误提示。
- 网络超时或断连时显示通用错误消息。
- 批量操作部分失败时显示成功/失败计数。
## Testing Strategy
### 测试框架
- **单元测试**: `pytest`
- **属性测试**: `hypothesis`Python property-based testing 库)
- **每个属性测试最少运行 100 次迭代**
### 属性测试Property-Based Tests
每个 Correctness Property 对应一个属性测试,使用 `hypothesis``@given` 装饰器生成随机输入。
测试标签格式: `Feature: knowledge-tenant-view, Property {number}: {property_text}`
| Property | 测试描述 | 生成策略 |
|----------|---------|---------|
| Property 1 | 生成随机 KnowledgeEntry 列表(混合 tenant_id、is_active验证 `get_tenant_summary()` 聚合正确性 | `st.lists(st.builds(KnowledgeEntry, tenant_id=st.sampled_from([...]), is_active=st.booleans()))` |
| Property 2 | 验证 `get_tenant_summary()` 返回列表按 entry_count 降序 | 复用 Property 1 的生成策略 |
| Property 3 | 生成随机 tenant_id + category + verified 组合,验证过滤结果一致性 | `st.sampled_from(tenant_ids)`, `st.sampled_from(categories)`, `st.sampled_from(['true','false',''])` |
| Property 4 | 生成随机 page/per_page验证分页切片正确性 | `st.integers(min_value=1, max_value=10)` for page/per_page |
| Property 5 | 生成随机 tenant_id 和条目数据,验证新建条目的 tenant_id | `st.text(min_size=1, max_size=50)` for tenant_id |
| Property 6 | 生成随机搜索词和 tenant_id验证搜索结果范围 | `st.text()` for query, `st.sampled_from(tenant_ids)` |
| Property 7 | 生成随机 tenant_id验证统计数据与手动聚合一致 | `st.sampled_from(tenant_ids)` + `st.none()` |
### 单元测试Unit Tests
单元测试聚焦于边界情况和具体示例:
- **边界**: 无活跃条目时 `get_tenant_summary()` 返回空数组
- **边界**: 不存在的 `tenant_id` 查询返回空列表 + `total=0`
- **示例**: 数据库异常时 API 返回 500
- **示例**: `add_knowledge_entry` 不传 `tenant_id` 时使用配置默认值
- **集成**: 前端 `loadTenantList()` → API → Manager 完整链路
### 测试配置
```python
from hypothesis import settings
@settings(max_examples=100)
```
每个属性测试函数头部添加注释引用设计文档中的 Property 编号,例如:
```python
# Feature: knowledge-tenant-view, Property 1: Tenant summary correctly aggregates active entries
@given(entries=st.lists(knowledge_entry_strategy(), min_size=0, max_size=50))
def test_tenant_summary_aggregation(entries):
...
```

View File

@@ -1,102 +0,0 @@
# Requirements Document
## Introduction
知识库租户分组展示功能。当前知识库管理页面以扁平列表展示所有知识条目,缺乏租户(市场)维度的组织结构。本功能将知识库页面改造为两层结构:第一层按租户分组展示汇总信息,第二层展示某个租户下的具体知识条目。数据库模型 `KnowledgeEntry` 已有 `tenant_id` 字段,后端需新增按租户聚合的 API前端需实现分组视图与钻取交互。
## Glossary
- **Dashboard**: Flask + Jinja2 + Bootstrap 5 构建的 Web 管理后台主页面(`dashboard.html`
- **Knowledge_Tab**: Dashboard 中 `#knowledge-tab` 区域,用于展示和管理知识库条目
- **Knowledge_API**: Flask Blueprint `knowledge_bp`,提供知识库相关的 REST API`/api/knowledge/*`
- **Knowledge_Manager**: `KnowledgeManager` 类,封装知识库的数据库查询与业务逻辑
- **Tenant**: 租户,即市场标识(如 `market_a``market_b`),通过 `KnowledgeEntry.tenant_id` 字段区分
- **Tenant_Summary**: 租户汇总信息,包含租户 ID、知识条目总数等聚合数据
- **Tenant_List_View**: 第一层视图,以卡片或列表形式展示所有租户的汇总信息
- **Tenant_Detail_View**: 第二层视图,展示某个租户下的具体知识条目列表(含分页、筛选)
- **KnowledgeEntry**: SQLAlchemy 数据模型,包含 `tenant_id``question``answer``category``confidence_score``usage_count``is_verified` 等字段
## Requirements
### Requirement 1: 租户汇总 API
**User Story:** 作为管理员,我希望后端提供按租户分组的知识库汇总接口,以便前端展示每个租户的知识条目统计。
#### Acceptance Criteria
1. WHEN a GET request is sent to `/api/knowledge/tenants`, THE Knowledge_API SHALL return a JSON array of Tenant_Summary objects, each containing `tenant_id`, `entry_count`, `verified_count`, and `category_distribution`
2. THE Knowledge_API SHALL only count active knowledge entries (`is_active == True`) in the Tenant_Summary aggregation
3. THE Knowledge_API SHALL sort the Tenant_Summary array by `entry_count` in descending order
4. WHEN no active knowledge entries exist, THE Knowledge_API SHALL return an empty JSON array with HTTP status 200
5. IF a database query error occurs, THEN THE Knowledge_API SHALL return an error response with HTTP status 500 and a descriptive error message
### Requirement 2: 租户条目列表 API
**User Story:** 作为管理员,我希望后端提供按租户筛选的知识条目分页接口,以便在点击某个租户后查看该租户下的具体知识条目。
#### Acceptance Criteria
1. WHEN a GET request with query parameter `tenant_id` is sent to `/api/knowledge`, THE Knowledge_API SHALL return only the knowledge entries belonging to the specified Tenant
2. THE Knowledge_API SHALL support pagination via `page` and `per_page` query parameters when filtering by `tenant_id`
3. THE Knowledge_API SHALL support `category` and `verified` query parameters for further filtering within a Tenant
4. WHEN the `tenant_id` parameter value does not match any existing entries, THE Knowledge_API SHALL return an empty knowledge list with `total` equal to 0 and HTTP status 200
5. THE Knowledge_Manager SHALL provide a method that accepts `tenant_id` as a filter parameter and returns paginated results
### Requirement 3: 租户列表视图(第一层)
**User Story:** 作为管理员,我希望知识库页面首先展示按租户分组的汇总卡片,以便快速了解各市场的知识库规模。
#### Acceptance Criteria
1. WHEN the Knowledge_Tab is activated, THE Dashboard SHALL display a Tenant_List_View showing one card per Tenant
2. THE Tenant_List_View SHALL display the following information for each Tenant: tenant_id租户名称, entry_count知识条目总数, verified_count已验证条目数
3. WHEN the Tenant_List_View is loading data, THE Dashboard SHALL display a loading spinner in the Knowledge_Tab area
4. WHEN no tenants exist, THE Dashboard SHALL display a placeholder message indicating that no knowledge entries are available
5. THE Tenant_List_View SHALL refresh its data when the user clicks a refresh button
### Requirement 4: 租户详情视图(第二层)
**User Story:** 作为管理员,我希望点击某个租户卡片后能查看该租户下的具体知识条目列表,以便管理和审核知识内容。
#### Acceptance Criteria
1. WHEN a user clicks on a Tenant card in the Tenant_List_View, THE Dashboard SHALL transition to the Tenant_Detail_View showing knowledge entries for the selected Tenant
2. THE Tenant_Detail_View SHALL display each knowledge entry with the following fields: question, answer, category, confidence_score, usage_count, is_verified status
3. THE Tenant_Detail_View SHALL provide a breadcrumb navigation showing "知识库 > {tenant_id}" to indicate the current context
4. WHEN the user clicks the breadcrumb "知识库" link, THE Dashboard SHALL navigate back to the Tenant_List_View
5. THE Tenant_Detail_View SHALL support pagination with configurable page size
6. THE Tenant_Detail_View SHALL support filtering by category and verification status
### Requirement 5: 租户详情视图中的知识条目操作
**User Story:** 作为管理员,我希望在租户详情视图中能对知识条目执行添加、删除、验证等操作,以便维护知识库内容。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL provide buttons for adding, deleting, verifying, and unverifying knowledge entries
2. WHEN a user adds a new knowledge entry in the Tenant_Detail_View, THE Knowledge_API SHALL associate the new entry with the currently selected Tenant by setting the `tenant_id` field
3. WHEN a user performs a batch operation (batch delete, batch verify, batch unverify) in the Tenant_Detail_View, THE Dashboard SHALL refresh the Tenant_Detail_View to reflect the updated data
4. WHEN a user deletes all entries for a Tenant, THE Dashboard SHALL navigate back to the Tenant_List_View and remove the empty Tenant card
5. IF a knowledge entry operation fails, THEN THE Dashboard SHALL display an error notification with the failure reason
### Requirement 6: 搜索功能适配
**User Story:** 作为管理员,我希望在租户详情视图中搜索知识条目时,搜索范围限定在当前租户内,以便精确查找。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL scope the knowledge search to the currently selected Tenant
2. WHEN a search query is submitted in the Tenant_Detail_View, THE Knowledge_API SHALL filter search results by the specified `tenant_id`
3. WHEN the search query is cleared, THE Dashboard SHALL restore the full paginated list for the current Tenant
4. THE Knowledge_Manager search method SHALL accept an optional `tenant_id` parameter to limit search scope
### Requirement 7: 统计信息适配
**User Story:** 作为管理员,我希望知识库统计面板在租户列表视图时展示全局统计,在租户详情视图时展示当前租户的统计,以便获取准确的上下文信息。
#### Acceptance Criteria
1. WHILE the Tenant_List_View is displayed, THE Dashboard SHALL show global knowledge statistics (total entries across all tenants, total verified entries, average confidence)
2. WHILE the Tenant_Detail_View is displayed, THE Dashboard SHALL show statistics scoped to the selected Tenant
3. WHEN a GET request with query parameter `tenant_id` is sent to `/api/knowledge/stats`, THE Knowledge_API SHALL return statistics filtered by the specified Tenant
4. WHEN the `tenant_id` parameter is omitted from the stats request, THE Knowledge_API SHALL return global statistics across all tenants

View File

@@ -1,157 +0,0 @@
# Implementation Plan: 知识库租户分组展示 (knowledge-tenant-view)
## Overview
将知识库管理页面从扁平列表改造为两层结构:第一层按租户分组展示汇总卡片,第二层展示租户下的知识条目列表。改造涉及 KnowledgeManager 业务逻辑层、Flask API 层、前端 dashboard.js 三个层面。
## Tasks
- [x] 1. KnowledgeManager 新增 get_tenant_summary 方法
- [x] 1.1 在 `src/knowledge_base/knowledge_manager.py` 中新增 `get_tenant_summary()` 方法
- 使用 SQLAlchemy `GROUP BY tenant_id` 聚合 `is_active == True` 的知识条目
- 返回包含 `tenant_id``entry_count``verified_count``category_distribution` 的字典列表
-`entry_count` 降序排列
- 数据库异常时返回空列表 `[]`,记录 error 日志
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [ ]* 1.2 为 get_tenant_summary 编写属性测试
- **Property 1: Tenant summary correctly aggregates active entries**
- **Property 2: Tenant summary sorted by entry_count descending**
- 使用 `hypothesis` 生成随机 KnowledgeEntry 列表,验证聚合正确性和排序
- **Validates: Requirements 1.1, 1.2, 1.3**
- [x] 2. KnowledgeManager 现有方法增加 tenant_id 过滤
- [x] 2.1 为 `get_knowledge_paginated()` 增加 `tenant_id` 可选参数
-`src/knowledge_base/knowledge_manager.py` 中修改方法签名,增加 `tenant_id: Optional[str] = None`
-`tenant_id` 不为 None 时,在查询中增加 `KnowledgeEntry.tenant_id == tenant_id` 过滤条件
- 返回结构不变,仅过滤范围缩小
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [ ]* 2.2 为 get_knowledge_paginated 的 tenant_id 过滤编写属性测试
- **Property 3: Knowledge entry filtering by tenant, category, and verified status**
- **Property 4: Pagination consistency with tenant filter**
- **Validates: Requirements 2.1, 2.2, 2.3**
- [x] 2.3 为 `search_knowledge()` 增加 `tenant_id` 可选参数
- 修改 `search_knowledge()``_search_by_embedding()``_search_by_keyword()` 方法签名
-`tenant_id` 不为 None 时,在查询中增加 tenant_id 过滤条件
- _Requirements: 6.2, 6.4_
- [ ]* 2.4 为 search_knowledge 的 tenant_id 过滤编写属性测试
- **Property 6: Search results scoped to tenant**
- **Validates: Requirements 6.2**
- [x] 2.5 为 `get_knowledge_stats()` 增加 `tenant_id` 可选参数
-`tenant_id` 不为 None 时,所有统计查询增加 tenant_id 过滤
- 返回结构中增加 `tenant_id` 字段(仅当按租户筛选时)
- _Requirements: 7.3, 7.4_
- [ ]* 2.6 为 get_knowledge_stats 的 tenant_id 过滤编写属性测试
- **Property 7: Stats scoped to tenant**
- **Validates: Requirements 7.3, 7.4**
- [x] 2.7 为 `add_knowledge_entry()` 增加 `tenant_id` 可选参数
-`tenant_id` 不为 None 时,新建条目的 `tenant_id` 设为该值
-`tenant_id` 为 None 时,使用 `get_config().server.tenant_id` 作为默认值
- _Requirements: 5.2_
- [ ]* 2.8 为 add_knowledge_entry 的 tenant_id 关联编写属性测试
- **Property 5: New entry tenant association**
- **Validates: Requirements 5.2**
- [x] 3. Checkpoint - 确保后端业务逻辑层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Knowledge API 层新增和修改端点
- [x] 4.1 在 `src/web/blueprints/knowledge.py` 中新增 `GET /api/knowledge/tenants` 端点
- 调用 `knowledge_manager.get_tenant_summary()` 返回租户汇总 JSON 数组
- 使用 `@handle_api_errors` 装饰器处理异常
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [x] 4.2 修改 `GET /api/knowledge` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `get_knowledge_paginated()`
- _Requirements: 2.1, 2.2, 2.3, 2.4_
- [x] 4.3 修改 `GET /api/knowledge/stats` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `get_knowledge_stats()`
- _Requirements: 7.3, 7.4_
- [x] 4.4 修改 `GET /api/knowledge/search` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `search_knowledge()`
- _Requirements: 6.2_
- [x] 4.5 修改 `POST /api/knowledge` 端点,从请求体读取 `tenant_id` 字段
-`tenant_id` 传递给 `add_knowledge_entry()`
- _Requirements: 5.2_
- [ ]* 4.6 为新增和修改的 API 端点编写单元测试
- 测试 `/api/knowledge/tenants` 返回正确的汇总数据
- 测试各端点的 `tenant_id` 参数过滤行为
- 测试空数据和异常情况
- _Requirements: 1.1, 1.4, 1.5, 2.4_
- [x] 5. Checkpoint - 确保后端 API 层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. 前端 Tenant_List_View租户列表视图
- [x] 6.1 在 `src/web/static/js/dashboard.js` 中实现 `loadTenantList()` 函数
- 请求 `GET /api/knowledge/tenants` 获取租户汇总数据
- 渲染租户卡片列表,每张卡片展示 `tenant_id``entry_count``verified_count`
- 添加加载中 spinner 状态
- 无租户时展示空状态占位提示
- 卡片点击事件绑定,调用 `loadTenantDetail(tenantId)`
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [x] 6.2 实现刷新按钮功能
- 在知识库 tab 区域添加刷新按钮,点击时重新调用 `loadTenantList()`
- _Requirements: 3.5_
- [x] 7. 前端 Tenant_Detail_View租户详情视图
- [x] 7.1 实现 `loadTenantDetail(tenantId, page)` 函数
- 请求 `GET /api/knowledge?tenant_id=X&page=P&per_page=N` 获取知识条目
- 渲染知识条目表格,展示 question、answer、category、confidence_score、usage_count、is_verified
- 实现分页控件
- 支持 category 和 verified 筛选下拉框
- _Requirements: 4.1, 4.2, 4.5, 4.6_
- [x] 7.2 实现面包屑导航 `renderBreadcrumb(tenantId)`
- 展示 "知识库 > {tenant_id}" 面包屑
- 点击 "知识库" 链接时调用 `loadTenantList()` 返回租户列表视图
- 管理 `currentTenantId` 状态变量控制视图层级
- _Requirements: 4.3, 4.4_
- [x] 7.3 在 Tenant_Detail_View 中集成知识条目操作按钮
- 复用现有的添加、删除、验证、取消验证按钮逻辑
- 添加知识条目时自动设置 `tenant_id` 为当前选中的租户
- 批量操作(批量删除、批量验证、批量取消验证)后刷新当前视图
- 删除所有条目后自动返回租户列表视图
- 操作失败时通过 `showNotification` 展示错误提示
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_
- [x] 8. 前端搜索和统计面板适配
- [x] 8.1 修改搜索功能,在 Tenant_Detail_View 中自动附加 `tenant_id` 参数
- 搜索请求附加 `&tenant_id=currentTenantId`
- 清空搜索时恢复当前租户的完整分页列表
- _Requirements: 6.1, 6.2, 6.3_
- [x] 8.2 修改 `loadKnowledgeStats()` 函数,根据视图层级请求不同统计
-`currentTenantId` 为 null 时请求全局统计
-`currentTenantId` 有值时请求 `GET /api/knowledge/stats?tenant_id=X`
- _Requirements: 7.1, 7.2_
- [x] 9. 前端 HTML 模板更新
- [x] 9.1 在 `src/web/templates/dashboard.html``#knowledge-tab` 区域添加必要的 DOM 容器
- 添加面包屑容器、租户卡片列表容器、租户详情容器
- 确保与现有 Bootstrap 5 样式一致
- _Requirements: 3.1, 4.3_
- [x] 10. Final checkpoint - 确保所有功能集成完成
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- 数据模型 `KnowledgeEntry` 已有 `tenant_id` 字段且已建索引,无需数据库迁移

View File

@@ -1,25 +0,0 @@
# Product Overview
TSP Assistant (TSP智能助手) is an AI-powered customer service and work order management system built for TSP (Telematics Service Provider) vehicle service providers.
## What It Does
- Intelligent dialogue with customers via WebSocket real-time chat and Feishu (Lark) bot integration
- Work order lifecycle management with AI-generated resolution suggestions
- Knowledge base with semantic search (TF-IDF + cosine similarity, optional local embedding model)
- Vehicle data querying by VIN
- Analytics dashboard with alerts, performance monitoring, and reporting
- Multi-tenant architecture — data is isolated by `tenant_id` across all core tables
- Feishu multi-dimensional table (多维表格) bidirectional sync for work orders
## Key Domain Concepts
- **Work Order (工单)**: A support ticket tied to a vehicle issue. Can be dispatched to module owners, tracked through statuses, and enriched with AI suggestions.
- **Knowledge Entry (知识库条目)**: Q&A pairs used for retrieval-augmented responses. Verified entries have higher confidence.
- **Tenant (租户)**: Logical isolation unit (e.g., a market or region). All major entities carry a `tenant_id`.
- **Agent**: A ReAct-style LLM agent with registered tools (knowledge search, vehicle query, analytics, Feishu messaging).
- **Chat Session (对话会话)**: Groups multi-turn conversations; tracks source (websocket, API, feishu_bot).
## Primary Language
The codebase, comments, log messages, and UI are predominantly in **Chinese (Simplified)**. Variable names and code structure follow English conventions.

View File

@@ -1,79 +0,0 @@
# Project Structure
```
├── src/ # Main application source
│ ├── main.py # TSPAssistant facade class (orchestrates all managers)
│ ├── agent_assistant.py # Agent-enhanced assistant variant
│ ├── agent/ # ReAct LLM agent
│ │ ├── react_agent.py # Agent loop with tool dispatch
│ │ └── llm_client.py # Agent-specific LLM client
│ ├── core/ # Core infrastructure
│ │ ├── models.py # SQLAlchemy ORM models (all entities)
│ │ ├── database.py # DatabaseManager singleton, session management
│ │ ├── llm_client.py # QwenClient (OpenAI-compatible LLM calls)
│ │ ├── cache_manager.py # In-memory + Redis caching
│ │ ├── redis_manager.py # Redis connection pool
│ │ ├── vector_store.py # Vector storage for embeddings
│ │ ├── embedding_client.py # Local embedding model client
│ │ ├── auth_manager.py # Authentication logic
│ │ └── ... # Performance, backup, query optimizer
│ ├── config/
│ │ └── unified_config.py # UnifiedConfig singleton (env → dataclasses)
│ ├── dialogue/ # Conversation management
│ │ ├── dialogue_manager.py # Message processing, work order creation
│ │ ├── conversation_history.py
│ │ └── realtime_chat.py # Real-time chat manager
│ ├── knowledge_base/
│ │ └── knowledge_manager.py # Knowledge CRUD, search, import
│ ├── analytics/ # Monitoring & analytics
│ │ ├── analytics_manager.py
│ │ ├── alert_system.py
│ │ ├── monitor_service.py
│ │ ├── token_monitor.py
│ │ └── ai_success_monitor.py
│ ├── integrations/ # External service integrations
│ │ ├── feishu_client.py # Feishu API client
│ │ ├── feishu_service.py # Feishu business logic
│ │ ├── feishu_longconn_service.py # Feishu event subscription (long-conn)
│ │ ├── workorder_sync.py # Feishu ↔ local work order sync
│ │ └── flexible_field_mapper.py # Feishu field mapping
│ ├── vehicle/
│ │ └── vehicle_data_manager.py
│ ├── utils/ # Shared helpers
│ │ ├── helpers.py
│ │ ├── encoding_helper.py
│ │ └── semantic_similarity.py
│ └── web/ # Web layer
│ ├── app.py # Flask app factory, middleware, blueprint registration
│ ├── service_manager.py # Lazy-loading service singleton registry
│ ├── decorators.py # @handle_errors, @require_json, @resolve_tenant_id, @rate_limit
│ ├── error_handlers.py # Unified API response helpers
│ ├── websocket_server.py # Standalone WebSocket server
│ ├── blueprints/ # Flask blueprints (one per domain)
│ │ ├── alerts.py, workorders.py, conversations.py, knowledge.py
│ │ ├── auth.py, tenants.py, chat.py, agent.py, vehicle.py
│ │ ├── analytics.py, monitoring.py, system.py
│ │ ├── feishu_sync.py, feishu_bot.py
│ │ └── test.py, core.py
│ ├── static/ # Frontend assets (JS, CSS)
│ └── templates/ # Jinja2 HTML templates
├── config/ # Runtime config files (field mappings)
├── data/ # SQLite DB file, system settings JSON
├── logs/ # Log files (per-startup subdirectories)
├── scripts/ # Migration and utility scripts
├── start_dashboard.py # Main entry point (Flask + WS + Feishu)
├── start_feishu_bot.py # Standalone Feishu bot entry point
├── init_database.py # DB initialization script
├── requirements.txt # Python dependencies
├── nginx.conf # Nginx reverse proxy config
└── .env / .env.example # Environment configuration
```
## Key Patterns
- **Singleton managers**: `db_manager`, `service_manager`, `get_config()` — instantiated once, imported globally.
- **Blueprint-per-domain**: Each functional area (workorders, alerts, knowledge, etc.) has its own Flask blueprint under `src/web/blueprints/`.
- **Service manager with lazy loading**: `ServiceManager` in `src/web/service_manager.py` provides thread-safe lazy initialization of all service instances. Blueprints access services through it.
- **Decorator-driven API patterns**: Common decorators in `src/web/decorators.py` handle error wrapping, JSON validation, tenant resolution, and rate limiting.
- **Multi-tenant by convention**: All DB queries should filter by `tenant_id`. The `@resolve_tenant_id` decorator extracts it from request body, query params, or session.
- **Config from env**: No hardcoded secrets. All configuration flows through `UnifiedConfig` which reads from `.env` via `python-dotenv`.

View File

@@ -1,67 +0,0 @@
# Tech Stack & Build
## Language & Runtime
- Python 3.11+
## Core Frameworks & Libraries
| Layer | Technology |
|---|---|
| Web framework | Flask 3.x + Flask-CORS |
| ORM / Database | SQLAlchemy 2.x (MySQL via PyMySQL, SQLite for dev) |
| Real-time comms | `websockets` library (standalone server on port 8765) |
| Caching | Redis 5.x client + hiredis |
| LLM integration | OpenAI-compatible API (default provider: Qwen/通义千问 via DashScope) |
| Embedding | `sentence-transformers` with `BAAI/bge-small-zh-v1.5` (local, optional) |
| NLP | jieba (Chinese word segmentation), scikit-learn (TF-IDF) |
| Feishu SDK | `lark-oapi` 1.3.x (event subscription 2.0, long-connection mode) |
| Data validation | pydantic 2.x, marshmallow |
| Auth | JWT (`pyjwt`), SHA-256 password hashing |
| Monitoring | psutil (in-process), Prometheus + Grafana (Docker) |
## Configuration
- All config loaded from environment variables via `python-dotenv``src/config/unified_config.py`
- Singleton `UnifiedConfig` with typed dataclasses (`DatabaseConfig`, `LLMConfig`, `ServerConfig`, etc.)
- `.env` file at project root (see `.env.example` for all keys)
## Common Commands
```bash
# Install dependencies
pip install -r requirements.txt
# Initialize / migrate database
python init_database.py
# Start the full application (Flask + WebSocket + Feishu long-conn)
python start_dashboard.py
# Start only the Feishu bot long-connection client
python start_feishu_bot.py
# Run tests
pytest
# Code formatting
black .
isort .
# Linting
flake8
mypy .
```
## Deployment
- Docker + docker-compose (MySQL 8, Redis 7, Nginx, Prometheus, Grafana)
- Nginx reverse proxy in front of Flask (port 80/443 → 5000)
- Default ports: Flask 5000, WebSocket 8765, Redis 6379, MySQL 3306
## Code Quality Tools
- `black` for formatting (PEP 8)
- `isort` for import sorting
- `flake8` for linting
- `mypy` for type checking

26
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"files.autoGuessEncoding": false,
"files.encoding": "utf8",
"files.eol": "\n",
"[python]": {
"files.encoding": "utf8",
"files.eol": "\n"
},
"[json]": {
"files.encoding": "utf8"
},
"[javascript]": {
"files.encoding": "utf8"
},
"[html]": {
"files.encoding": "utf8"
},
"[css]": {
"files.encoding": "utf8"
},
"[markdown]": {
"files.encoding": "utf8"
},
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe",
"Codegeex.RepoIndex": true
}

View File

@@ -1,96 +0,0 @@
# AGENTS.md — TSP 智能助手
> AI 助手代码导航指南。详细文档见 `.agents/summary/index.md`。
## 项目概述
TSP 智能助手是一个 AI 驱动的多租户客服与工单管理系统。Python 3.11+ / Flask 3.x / SQLAlchemy 2.x。入口文件 `start_dashboard.py`
## 目录导航
```
src/
├── config/unified_config.py # UnifiedConfig 单例,所有配置从 .env 加载
├── core/
│ ├── models.py # 所有 SQLAlchemy ORM 模型11 个表)
│ ├── database.py # DatabaseManager 单例session 管理
│ ├── llm_client.py # LLMClient — OpenAI-compatible API (Qwen)
│ ├── auth_manager.py # JWT + SHA-256 认证
│ ├── cache_manager.py # Redis 缓存
│ └── vector_store.py # Embedding 向量存储
├── dialogue/
│ ├── dialogue_manager.py # 核心对话处理,调用 LLM + 知识库
│ └── realtime_chat.py # WebSocket 实时聊天管理
├── knowledge_base/
│ └── knowledge_manager.py # 知识库 CRUD + TF-IDF/Embedding 搜索
├── integrations/
│ ├── feishu_service.py # 飞书 API 客户端
│ ├── feishu_longconn_service.py # 飞书长连接事件订阅
│ └── workorder_sync.py # 工单 ↔ 飞书多维表格双向同步
├── agent/react_agent.py # ReAct Agent工具调度循环
├── analytics/
│ ├── analytics_manager.py # 数据分析
│ └── alert_system.py # 预警规则引擎
└── web/
├── app.py # Flask 应用工厂 + 路由
├── service_manager.py # 懒加载服务注册中心
├── decorators.py # @handle_errors, @require_json, @resolve_tenant_id, @rate_limit
├── websocket_server.py # 独立 WebSocket 服务器 (port 8765)
└── blueprints/ # 16 个 Flask Blueprint每个领域一个文件
```
## 关键模式
### 多租户
所有核心表含 `tenant_id` 字段。`@resolve_tenant_id` 装饰器从请求中提取。查询时必须过滤 `tenant_id`
### 服务获取
Blueprint 通过 `ServiceManager` 懒加载获取服务实例,不要直接实例化业务类。
### API 装饰器栈
典型 API 端点模式:
```python
@bp.route('/api/xxx', methods=['POST'])
@handle_errors()
@require_json(['field1', 'field2'])
def create_xxx():
...
```
### 配置
所有配置通过 `get_config()` 获取,从 `.env` 加载。不要硬编码配置值。参考 `.env.example` 了解所有可用变量。
## 入口与启动
- `start_dashboard.py` — 主入口,启动 Flask + WebSocket + 飞书长连接3 个线程)
- `start_feishu_bot.py` — 独立飞书机器人入口
- `init_database.py` — 数据库初始化
## 数据库
- 开发: SQLite (`data/tsp_assistant.db`)
- 生产: MySQL via PyMySQL
- ORM: SQLAlchemy 2.x模型定义在 `src/core/models.py`
- 核心表: `Tenant`, `WorkOrder`, `ChatSession`, `Conversation`, `KnowledgeEntry`, `Alert`, `Analytics`, `VehicleData`, `User`
## 外部集成
- **LLM**: Qwen/DashScope (OpenAI-compatible API),通过 `LLMClient` 调用
- **飞书**: `lark-oapi` SDK长连接模式接收消息API 模式发送消息和操作多维表格
- **Redis**: 可选缓存层,`REDIS_ENABLED` 控制
## 详细文档
完整文档体系在 `.agents/summary/` 目录:
- `index.md` — 文档索引(推荐作为 AI 上下文入口)
- `architecture.md` — 架构图和设计模式
- `components.md` — 组件职责
- `interfaces.md` — API 列表
- `data_models.md` — 数据模型 ER 图
- `workflows.md` — 关键流程时序图
- `dependencies.md` — 依赖说明
## Custom Instructions
<!-- This section is for human and agent-maintained operational knowledge.
Add repo-specific conventions, gotchas, and workflow rules here.
This section is preserved exactly as-is when re-running codebase-summary. -->

618
README.md
View File

@@ -1,130 +1,542 @@
# TSP 智能助手
# TSP智能助手 (TSP Assistant)
AI 驱动的多租户客服与工单管理系统支持飞书机器人、WebSocket 实时对话、知识库语义搜索。
[![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](version.json)
[![Python](https://img.shields.io/badge/python-3.11+-green.svg)](requirements.txt)
[![Docker](https://img.shields.io/badge/docker-supported-blue.svg)](Dockerfile)
[![License](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
[![Status](https://img.shields.io/badge/status-production-ready-brightgreen.svg)]()
## 功能概览
> 基于大语言模型的智能客服系统专为TSPTelematics Service Provider车辆服务提供商设计
- **智能对话** — WebSocket 实时聊天 + 飞书机器人(长连接模式),按租户隔离知识库
- **工单管理** — 创建、编辑、删除、飞书多维表格双向同步AI 生成处理建议
- **知识库** — TF-IDF + 可选 Embedding 语义搜索,支持文件导入、人工验证
- **多租户** — 数据按 tenant_id 隔离,每个租户独立的系统提示词和飞书群绑定
- **数据分析** — 工单趋势、预警统计、满意度分析,支持按租户筛选
- **预警系统** — 自定义规则、多级别预警、批量管理
- **Agent 模式** — ReAct 风格 LLM Agent支持工具调度知识搜索、车辆查询、飞书消息等
- **系统管理** — 模块权限控制、流量/成本/安全配置、Token 监控
## 🚀 项目特色
## 快速开始
### 🧠 智能Agent架构
- **多工具集成**: 知识库搜索、工单管理、数据分析、通知推送等10+工具
- **智能规划**: 基于目标驱动的任务规划和执行
- **自主学习**: 从用户反馈中持续优化响应质量
- **实时监控**: 主动监控系统状态和异常情况
- **模块化重构**: 后端服务Agent, 车辆数据, 分析, 测试)蓝图化,提高可维护性和扩展性
- **前端模块化**: 引入ES6模块化架构优化UI组件、状态管理和API服务
### 💬 智能对话系统
- **实时通信**: WebSocket支持毫秒级响应已修复连接稳定性问题
- **上下文理解**: 多轮对话记忆和上下文关联
- **VIN识别**: 自动识别车辆VIN码并获取实时数据
- **知识库集成**: 基于TF-IDF和余弦相似度的智能检索
- **自定义提示词**: 支持飞书同步和实时对话不同场景的LLM提示词
### 📊 数据驱动分析
- **真实数据**: 基于数据库的真实性能趋势分析
- **多维度统计**: 工单、预警、满意度、性能指标
- **可视化展示**: Chart.js图表直观的数据呈现
- **系统监控**: 实时CPU、内存、健康状态监控
- **专属蓝图**: 独立的数据分析API模块提供专业数据报告导出
### 🔧 企业级管理
- **多环境部署**: 开发、测试、生产环境隔离
- **版本控制**: 完整的版本管理和变更日志
- **热更新**: 支持前端文件热更新,无需重启服务
- **自动备份**: 更新前自动备份,支持一键回滚
- **飞书集成**: 支持飞书多维表格数据同步和管理
- **统一错误处理**: 后端API统一异常处理提高系统健壮性
## 🏗️ 系统架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端界面 │ │ 后端服务 │ │ 数据存储 │
│ │ │ │ │ │
│ • 仪表板 │◄──►│ • Flask API │◄──►│ • MySQL DB │
│ • 智能对话 │ │ • WebSocket │ │ • Redis缓存 │
│ • Agent管理 │ │ • Agent核心 │ │ • 知识库 │
│ • 数据分析 │ │ • LLM集成 │ │ • 工单系统 │
│ • 备份管理 │ │ • 备份系统 │ │ • 车辆数据 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ 监控系统 │
│ │
│ • Prometheus │
│ • Grafana │
│ • Nginx代理 │
└─────────────────┘
```
## 🎯 核心功能
### 1. 智能对话 💬
- **多轮对话**: 支持上下文关联的连续对话
- **VIN识别**: 自动识别车辆VIN并获取实时数据
- **知识库检索**: 智能匹配相关技术文档和解决方案
- **工单创建**: 对话中直接创建和关联工单
- **错误修复**: 解决了WebSocket连接TypeError问题
### 2. Agent管理 🤖
- **工具管理**: 10+内置工具,支持自定义工具注册
- **执行监控**: 实时监控Agent任务执行状态
- **性能统计**: 工具使用频率和成功率分析
- **智能规划**: 基于目标的任务分解和执行
- **专用蓝图**: Agent相关API已独立为蓝图管理
### 3. 工单系统 📋
- **AI建议**: 基于知识库生成工单处理建议
- **人工审核**: 支持人工输入和AI建议对比
- **相似度评估**: 自动计算AI与人工建议的相似度
- **知识库更新**: 高相似度建议自动入库
- **飞书AI提示词**: 针对飞书同步场景提供更详细的AI建议提示词
### 4. 知识库管理 📚
- **多格式支持**: TXT、PDF、DOC、DOCX、MD文件
- **智能提取**: 自动从文档中提取Q&A对
- **向量化检索**: TF-IDF + 余弦相似度搜索
- **质量验证**: 支持知识条目验证和置信度设置
### 5. 数据分析 📊
- **实时趋势**: 基于真实数据的性能趋势分析
- **多维度统计**: 工单、预警、满意度等关键指标
- **系统健康**: CPU、内存、响应时间监控
- **可视化展示**: 丰富的图表和仪表板
- **专用蓝图**: 数据分析API已独立为蓝图管理并支持Excel报告导出
### 6. 系统设置 ⚙️
- **API管理**: 支持多种LLM提供商配置
- **模型参数**: 温度、最大令牌数等参数调节
- **端口配置**: Web服务和WebSocket端口管理
- **日志级别**: 灵活的日志级别控制
- **数据库健壮性**: 优化了数据库连接配置和错误处理
### 7. 飞书集成 📱
- **多维表格同步**: 自动同步飞书多维表格数据
- **字段映射**: 智能映射飞书字段到本地数据库
- **实时更新**: 支持增量同步和全量同步
- **数据预览**: 同步前预览数据,确保准确性
- **统一管理**: 飞书功能集成到主仪表板
## 🛠️ 技术栈
### 后端技术
- **Python 3.11+**: 核心开发语言
- **Flask 2.3+**: Web框架和API服务
- **SQLAlchemy 2.0+**: ORM数据库操作
- **WebSocket**: 实时通信支持
- **psutil**: 系统资源监控
- **Redis**: 缓存和会话管理
### 前端技术
- **Bootstrap 5**: UI框架
- **Chart.js**: 数据可视化
- **JavaScript ES6+**: 前端逻辑
- **WebSocket**: 实时通信客户端
### AI/ML技术
- **大语言模型**: 支持OpenAI、通义千问等
- **TF-IDF**: 文本向量化
- **余弦相似度**: 语义相似度计算
- **Agent框架**: 智能任务规划
- **Transformers**: 预训练模型支持
### 部署运维
- **Docker**: 容器化部署
- **Docker Compose**: 多服务编排
- **Nginx**: 反向代理和静态文件服务
- **Prometheus**: 监控数据收集
- **Grafana**: 监控仪表板
- **MySQL 8.0**: 主数据库
- **Redis 7**: 缓存服务
## 🚀 快速开始
### 环境要求
#### Docker部署推荐
- Docker 20.10+
- Docker Compose 2.0+
- 4GB+ 可用内存
- 10GB+ 可用磁盘空间
#### 本地部署
- Python 3.11+
- Node.js 16+ (可选,用于前端构建)
- MySQL 8.0+ 或 SQLite
- Redis 7+ (可选)
- Git
### 🐳 Docker部署推荐
1. **克隆项目**
```bash
git clone http://jeason.online:3000/zhaojie/assist.git
cd assist
```
2. **一键启动所有服务**
```bash
# 使用部署脚本
chmod +x scripts/docker_deploy.sh
./scripts/docker_deploy.sh start
# 或直接使用docker-compose
docker-compose up -d
```
3. **访问系统**
- **TSP助手**: http://localhost:5000
- **Nginx代理**: http://localhost
- **Prometheus监控**: http://localhost:9090
- **Grafana仪表板**: http://localhost:3000 (admin/admin123456)
4. **服务管理**
```bash
# 查看服务状态
./scripts/docker_deploy.sh status
# 查看日志
./scripts/docker_deploy.sh logs tsp-assistant
# 停止服务
./scripts/docker_deploy.sh stop
# 重启服务
./scripts/docker_deploy.sh restart
```
### 💻 本地部署
1. **克隆项目**
```bash
git clone http://jeason.online:3000/zhaojie/assist.git
cd assist
```
2. **安装依赖**
```bash
pip install -r requirements.txt
cp .env.example .env # 编辑填入 LLM API Key、飞书凭证等
```
3. **初始化数据库**
```bash
python init_database.py
```
4. **启动服务**
```bash
python start_dashboard.py
```
访问 http://localhost:5000默认账号 `admin` / `admin123`
5. **访问系统**
- 打开浏览器访问: `http://localhost:5000`
- 默认端口: 5000 (可在系统设置中修改)
## 系统架构
```mermaid
graph TB
subgraph Clients["客户端"]
Browser["浏览器 Dashboard"]
FeishuBot["飞书机器人"]
WSClient["WebSocket"]
end
subgraph App["应用层"]
Flask["Flask :5000"]
WS["WebSocket :8765"]
FeishuLC["飞书长连接"]
end
subgraph Services["业务层"]
DM["对话管理"]
KM["知识库"]
WOS["工单同步"]
Agent["ReAct Agent"]
end
subgraph Infra["基础设施"]
DB["SQLAlchemy"]
LLM["Qwen API"]
Cache["Redis"]
end
Clients --> App
App --> Services
Services --> Infra
### Windows快速启动
```cmd
# 双击运行
快速启动.bat
```
## 项目结构
## 📖 使用指南
```
src/
├── config/ # 配置管理unified_config
├── core/ # 基础设施数据库、LLM、缓存、认证、ORM 模型)
├── dialogue/ # 对话管理realtime_chat、dialogue_manager
├── knowledge_base/ # 知识库(搜索、导入、验证)
├── analytics/ # 监控与分析预警、Token、AI 成功率)
├── integrations/ # 外部集成(飞书客户端、工单同步)
├── agent/ # ReAct Agent工具调度
├── vehicle/ # 车辆数据管理
├── utils/ # 通用工具
└── web/ # Web 层
├── app.py # Flask 应用
├── blueprints/ # API 蓝图16 个,每个领域一个文件)
├── service_manager.py # 懒加载服务注册
├── static/ # 前端资源 (JS/CSS)
└── templates/ # Jinja2 模板
### 基础操作
1. **智能对话**
- 在"智能对话"页面输入问题
- 系统自动检索知识库并生成回答
- 支持VIN码识别和车辆数据查询
2. **工单管理**
- 创建工单并获取AI建议
- 人工输入解决方案
- 系统自动评估相似度并更新知识库
3. **知识库维护**
- 手动添加Q&A对
- 上传文档自动提取知识
- 设置置信度和验证状态
4. **系统监控**
- 查看实时性能趋势
- 监控系统健康状态
- 管理预警和通知
### 高级功能
1. **Agent工具管理**
- 查看工具使用统计
- 注册自定义工具
- 监控执行历史
2. **数据分析**
- 多维度数据统计
- 自定义时间范围
- 导出分析报告
3. **系统配置**
- API和模型参数配置
- 端口和日志级别设置
- 环境变量管理
## 🔄 部署与更新
### 多环境部署
```bash
# 开发环境
python scripts/update_manager.py auto-update --source . --environment development
# 测试环境
python scripts/update_manager.py auto-update --source . --environment staging
# 生产环境
python scripts/update_manager.py auto-update --source . --environment production
```
## 环境变量
### 版本管理
```bash
# 更新版本号
python version.py increment --type minor
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `SECRET_KEY` | Flask session 密钥 | 随机生成 |
| `DATABASE_URL` | 数据库连接串 | SQLite |
| `LLM_BASE_URL` | LLM API 地址 | DashScope |
| `LLM_API_KEY` | LLM API 密钥 | — |
| `LLM_MODEL` | 模型名称 | qwen-plus-latest |
| `FEISHU_APP_ID` | 飞书应用 ID | — |
| `FEISHU_APP_SECRET` | 飞书应用密钥 | — |
| `FEISHU_APP_TOKEN` | 飞书多维表格 App Token | — |
| `FEISHU_TABLE_ID` | 飞书多维表格 Table ID | — |
| `REDIS_HOST` | Redis 地址 | localhost |
| `REDIS_ENABLED` | 启用 Redis 缓存 | True |
| `EMBEDDING_ENABLED` | 启用 Embedding 语义搜索 | True |
| `EMBEDDING_MODEL` | Embedding 模型 | BAAI/bge-small-zh-v1.5 |
# 添加变更日志
python version.py changelog --message "新功能描述"
完整变量列表见 `.env.example`
# 创建发布标签
python version.py tag --message "Release v1.3.0"
```
## 技术栈
### 热更新
```bash
# 热更新(无需重启)
python scripts/update_manager.py hot-update --source ./new_version --environment production
| 层级 | 技术 |
|---|---|
| Web 框架 | Flask 3.x + Flask-CORS |
| ORM | SQLAlchemy 2.x (MySQL / SQLite) |
| 实时通信 | websockets (port 8765) |
| 缓存 | Redis 5.x + hiredis |
| LLM | Qwen/DashScope (OpenAI-compatible) |
| Embedding | sentence-transformers + BAAI/bge-small-zh-v1.5 (可选) |
| NLP | jieba + scikit-learn (TF-IDF) |
| 飞书 | lark-oapi 1.3.x |
| 认证 | JWT + SHA-256 |
# 自动更新(智能选择)
python scripts/update_manager.py auto-update --source ./new_version --environment production
```
## 数据库
## 📊 系统监控
- **开发**: SQLite (`data/tsp_assistant.db`)
- **生产**: MySQL via PyMySQL
- **核心表**: Tenant, WorkOrder, ChatSession, Conversation, KnowledgeEntry, Alert, Analytics, VehicleData, User
### 健康检查
- **API状态**: `/api/health`
- **服务监控**: 自动健康检查和故障恢复
- **性能指标**: 响应时间、吞吐量、错误率
## 部署
### 日志管理
- **应用日志**: `logs/tsp_assistant.log`
- **访问日志**: Nginx访问日志
- **错误追踪**: 详细的错误堆栈信息
- Docker + docker-compose (MySQL 8, Redis 7, Nginx, Prometheus, Grafana)
- Nginx 反向代理 (80/443 → 5000)
- 默认端口: Flask 5000, WebSocket 8765, Redis 6379, MySQL 3306
## 🔧 配置说明
## 详细文档
### Docker环境变量
```bash
# 数据库配置
DATABASE_URL=mysql+pymysql://tsp_user:tsp_password@mysql:3306/tsp_assistant?charset=utf8mb4
REDIS_URL=redis://redis:6379/0
完整技术文档见 `.agents/summary/` 目录,推荐从 `index.md` 开始阅读。
# LLM配置
LLM_PROVIDER=openai
LLM_API_KEY=your_api_key
LLM_MODEL=gpt-3.5-turbo
# 服务配置
SERVER_PORT=5000
WEBSOCKET_PORT=8765
LOG_LEVEL=INFO
TZ=Asia/Shanghai
```
### Docker服务配置
#### 主要服务
- **tsp-assistant**: 主应用服务 (端口: 5000, 8765)
- **mysql**: MySQL数据库 (端口: 3306)
- **redis**: Redis缓存 (端口: 6379)
- **nginx**: 反向代理 (端口: 80, 443)
#### 监控服务
- **prometheus**: 监控数据收集 (端口: 9090)
- **grafana**: 监控仪表板 (端口: 3000)
#### 数据卷
- `mysql_data`: MySQL数据持久化
- `redis_data`: Redis数据持久化
- `prometheus_data`: Prometheus数据持久化
- `grafana_data`: Grafana配置和数据持久化
### 配置文件
- `config/llm_config.py`: LLM客户端配置
- `config/integrations_config.json`: 飞书集成配置
- `nginx.conf`: Nginx反向代理配置
- `monitoring/prometheus.yml`: Prometheus监控配置
- `init.sql`: 数据库初始化脚本
- `docker-compose.yml`: Docker服务编排配置
- `Dockerfile`: 应用镜像构建配置
## 🤝 贡献指南
### 开发流程
1. Fork项目到个人仓库
2. 创建功能分支: `git checkout -b feature/new-feature`
3. 提交更改: `git commit -m "Add new feature"`
4. 推送分支: `git push origin feature/new-feature`
5. 创建Pull Request
### 代码规范
- Python代码遵循PEP 8规范
- JavaScript使用ES6+语法
- 提交信息使用约定式提交格式
- 新功能需要添加相应的测试
## 📝 更新日志
### v2.1.0 (2025-12-08) - 全面架构优化与问题修复
- ⚙️ **后端架构重构**:
- 将Agent、车辆数据、数据分析、API测试相关路由拆分为独立蓝图。
- 精简 `app.py` 主应用文件,提升模块化和可维护性。
- 引入统一错误处理装饰器和依赖注入机制。
- 🎨 **前端架构优化**:
- 实现了JavaScript模块化架构划分 `core`, `services`, `components` 目录。
- 引入了统一状态管理 (`store.js`) 和API服务 (`api.js`)。
- 优化了通知管理和预警显示组件。
- 🛠️ **关键问题修复**:
- 修复了WebSocket连接中 `TypeError: missing 1 required positional argument: 'path'` 错误。
- 改进了数据库连接的健壮性优化MySQL连接池配置并增强了异常处理和重连机制。
- 解决了 `generator didn't stop` 错误,确保数据库会话的正确关闭。
- 增强了预警系统异常处理,并在规则检查失败时生成系统预警。
- 优化了API错误响应包含更详细的错误信息。
-**新功能增强**:
- 为飞书同步和实时对话场景引入了不同的LLM提示词提升AI建议的针对性。
- 增加了对`Analysising`工单状态的映射处理。
### v2.0.0 (2025-09-22) - Docker环境全面升级
- 🐳 **Docker环境重构**: 升级到Python 3.11,优化镜像构建
- 🐳 **多服务编排**: MySQL 8.0 + Redis 7 + Nginx + Prometheus + Grafana
- 🐳 **监控系统**: 集成Prometheus监控和Grafana仪表板
- 🐳 **安全增强**: 非root用户运行数据卷隔离
- 🐳 **部署脚本**: 一键部署脚本,支持启动/停止/重启/清理
- 🔧 **知识库搜索修复**: 简化搜索算法,提升检索准确率
- 🔧 **批量删除优化**: 修复外键约束和缓存问题
- 🔧 **日志编码修复**: 解决中文乱码问题
- 📊 **可视化增强**: 修复预警、性能、满意度图表显示
- 📚 **文档更新**: 完整的Docker部署和使用指南
### v1.4.0 (2025-09-19)
- ✅ 飞书集成功能:支持飞书多维表格数据同步
- ✅ 页面功能合并:飞书同步页面合并到主仪表板
- ✅ 数据库架构优化:扩展工单表字段,支持飞书数据
- ✅ 代码重构优化:大文件拆分,降低运行风险
- ✅ 字段映射完善:智能映射飞书字段到本地数据库
- ✅ 数据库初始化改进:集成字段迁移到初始化流程
### v1.3.0 (2025-09-17)
- ✅ 数据库架构优化MySQL主数据库+SQLite备份系统
- ✅ 工单详情API修复解决数据库会话管理问题
- ✅ 备份管理系统自动备份MySQL数据到SQLite
- ✅ 数据库状态监控实时监控MySQL和SQLite状态
- ✅ 备份管理API支持数据备份和恢复操作
### v1.2.0 (2025-09-16)
- ✅ 系统设置扩展API管理、模型参数配置、端口管理
- ✅ 真实数据分析:修复性能趋势图表显示问题
- ✅ 工单AI建议功能智能生成处理建议
- ✅ 知识库搜索优化:提升检索准确率
- ✅ Agent管理改进工具使用统计和自定义工具
### v1.1.0 (2025-09-16)
- ✅ 工单AI建议功能
- ✅ 知识库搜索优化
- ✅ Agent管理改进
### v1.0.0 (2024-01-01)
- ✅ 初始版本发布
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
## 🔧 故障排除
### Docker部署问题
#### 常见问题
1. **端口冲突**
```bash
# 检查端口占用
netstat -tulpn | grep :5000
# 修改docker-compose.yml中的端口映射
```
2. **内存不足**
```bash
# 检查Docker资源使用
docker stats
# 增加Docker内存限制或关闭其他服务
```
3. **数据库连接失败**
```bash
# 检查MySQL服务状态
docker-compose logs mysql
# 等待数据库完全启动约30秒
```
4. **权限问题**
```bash
# 给脚本添加执行权限
chmod +x scripts/docker_deploy.sh
# 检查文件权限
ls -la scripts/
```
#### 日志查看
```bash
# 查看所有服务日志
docker-compose logs -f
# 查看特定服务日志
docker-compose logs -f tsp-assistant
docker-compose logs -f mysql
docker-compose logs -f redis
```
#### 服务重启
```bash
# 重启特定服务
docker-compose restart tsp-assistant
# 重启所有服务
docker-compose down && docker-compose up -d
```
### 性能优化
#### Docker资源限制
```yaml
# 在docker-compose.yml中添加资源限制
services:
tsp-assistant:
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
```
#### 数据库优化
```sql
-- MySQL性能优化
SET GLOBAL innodb_buffer_pool_size = 1G;
SET GLOBAL max_connections = 200;
```
## 📞 支持与联系
- **项目地址**: http://jeason.online:3000/zhaojie/assist
- **问题反馈**: 请在Issues中提交问题
- **功能建议**: 欢迎提交Feature Request
- **Docker问题**: 请提供docker-compose logs输出
## 🙏 致谢
感谢所有为项目做出贡献的开发者和用户!
---
**TSP智能助手** - 让车辆服务更智能,让客户体验更美好! 🚗✨

Binary file not shown.

262
auto_push.bat Normal file
View File

@@ -0,0 +1,262 @@
@echo off
chcp 65001 >nul
echo ========================================
echo TSP智能助手 - 自动推送脚本
echo ========================================
echo.
:: 检查Git状态
echo [1/4] 检查Git状态...
git status --porcelain >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Git未初始化或不在Git仓库中
pause
exit /b 1
)
:: 显示当前状态
echo 📋 当前Git状态:
git status --short
echo.
:: 询问是否继续
set /p confirm="是否继续推送? (y/n): "
if /i "%confirm%" neq "y" (
echo 操作已取消
pause
exit /b 0
)
:: 检查是否有更改需要提交
echo.
echo [2/4] 检查更改状态...
:: 启用延迟变量扩展
setlocal enabledelayedexpansion
:: 检查未暂存的更改
git diff --quiet
set has_unstaged=%errorlevel%
:: 检查已暂存的更改
git diff --cached --quiet
set has_staged=%errorlevel%
:: 检查未跟踪的文件
git ls-files --others --exclude-standard >nul 2>&1
set has_untracked=%errorlevel%
if %has_unstaged% equ 0 if %has_staged% equ 0 if %has_untracked% neq 0 (
echo 没有检测到任何更改,无需提交
echo.
echo ✅ 工作区干净,无需推送
pause
exit /b 0
)
:: 显示详细状态
echo 📊 详细状态信息:
echo 未暂存更改:
if %has_unstaged% neq 0 (
git diff --name-only
) else (
echo
)
echo 已暂存更改:
if %has_staged% neq 0 (
git diff --cached --name-only
) else (
echo
)
echo 未跟踪文件:
if %has_untracked% neq 0 (
git ls-files --others --exclude-standard
) else (
echo
)
echo.
:: 添加所有更改
echo 添加所有更改到暂存区...
git add .
if %errorlevel% neq 0 (
echo ❌ 添加文件失败
pause
exit /b 1
)
echo ✅ 文件已添加到暂存区
:: 检查markdown文件修改并生成智能提交信息
echo.
echo [3/4] 分析markdown文件并生成提交信息...
:: 检查是否有markdown文件修改
set md_files=
for /f "tokens=*" %%f in ('git diff --name-only --cached 2^>nul ^| findstr /i "\.md$"') do (
set md_files=!md_files! %%f
)
for /f "tokens=*" %%f in ('git diff --name-only 2^>nul ^| findstr /i "\.md$"') do (
set md_files=!md_files! %%f
)
set commit_msg=
if not "%md_files%"=="" (
echo 📝 检测到markdown文件修改:
echo %md_files%
echo.
:: 提取markdown文件的主要内容
set commit_title=
set commit_type=docs
:: 检查是否有修复相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "修复\|解决\|问题\|错误"') do (
set commit_type=fix
set commit_title=修复问题
goto :found_fix
)
)
)
:: 检查是否有新功能相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "功能\|新增\|添加\|实现"') do (
set commit_type=feat
set commit_title=新增功能
goto :found_feature
)
)
)
:: 检查是否有优化相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "优化\|性能\|改进\|提升"') do (
set commit_type=perf
set commit_title=性能优化
goto :found_optimization
)
)
)
:: 提取文件标题
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /n "^#" ^| head -1') do (
set line=%%l
set line=!line:*:=!
set line=!line:# =!
set line=!line:## =!
if "!line!" neq "" (
set commit_title=!line!
goto :found_title
)
)
)
)
:found_fix
:found_feature
:found_optimization
:found_title
if "%commit_title%"=="" (
set commit_title=更新文档记录
)
:: 生成提交信息
set commit_msg=%commit_type%: %commit_title%
echo 📋 生成的提交信息: %commit_msg%
echo.
) else (
echo 没有检测到markdown文件修改
set commit_msg=feat: 自动提交 - %date% %time%
)
:: 询问是否使用生成的提交信息
set /p confirm="是否使用此提交信息? (y/n/e编辑): "
if /i "%confirm%"=="e" (
set /p commit_msg="请输入自定义提交信息: "
) else if /i "%confirm%" neq "y" (
set /p commit_msg="请输入提交信息: "
)
:: 提交更改
echo 提交信息: %commit_msg%
git commit -m "%commit_msg%"
if %errorlevel% neq 0 (
echo ❌ 提交失败
pause
exit /b 1
)
echo ✅ 提交成功
:: 推送到远程仓库
echo.
echo [4/4] 推送到远程仓库...
:: 先尝试拉取最新更改
echo 🔄 检查远程更新...
git fetch origin main
if %errorlevel% neq 0 (
echo ⚠️ 无法获取远程更新,继续推送...
) else (
echo ✅ 远程更新检查完成
)
:: 推送到远程
git push origin main
if %errorlevel% neq 0 (
echo ❌ 推送失败
echo.
echo 💡 可能的原因:
echo - 网络连接问题
echo - 远程仓库权限不足
echo - 分支冲突
echo - 需要先拉取远程更改
echo.
echo 🔧 尝试自动解决冲突...
git pull origin main --rebase
if %errorlevel% equ 0 (
echo ✅ 冲突已解决,重新推送...
git push origin main
if %errorlevel% equ 0 (
echo ✅ 推送成功!
) else (
echo ❌ 重新推送失败
echo.
echo 🔧 建议手动解决:
echo 1. 运行: git pull origin main
echo 2. 解决冲突后运行: git push origin main
pause
exit /b 1
)
) else (
echo ❌ 无法自动解决冲突
echo.
echo 🔧 建议手动解决:
echo 1. 运行: git pull origin main
echo 2. 解决冲突后运行: git push origin main
pause
exit /b 1
)
) else (
echo ✅ 推送成功!
)
echo.
echo ========================================
echo ✅ 推送完成!
echo ========================================
echo 📊 提交统计:
git log --oneline -1
echo.
echo 🌐 远程仓库状态:
git status
echo.
pause

178
config/README.md Normal file
View File

@@ -0,0 +1,178 @@
# TSP智能助手配置说明
## 📋 配置文件概述
本目录包含TSP智能助手的核心配置文件包括LLM配置、集成配置等。
## 🤖 LLM配置
### 千问模型配置
本项目默认使用阿里云千问模型。要使用千问模型,请按以下步骤配置:
#### 1. 获取API密钥
1. 访问 [阿里云百炼平台](https://bailian.console.aliyun.com/)
2. 注册并登录账号
3. 创建应用并获取API密钥
#### 2. 配置API密钥
编辑 `config/llm_config.py` 文件,将 `api_key` 替换为您的实际API密钥
```python
QWEN_CONFIG = LLMConfig(
provider="openai",
api_key="sk-your-actual-qwen-api-key", # 替换为您的实际密钥
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-turbo",
temperature=0.7,
max_tokens=2000
)
```
#### 3. 可用的千问模型
- `qwen-turbo`: 快速响应,适合一般对话
- `qwen-plus`: 平衡性能和成本
- `qwen-max`: 最强性能,适合复杂任务
#### 4. 环境变量配置(可选)
您也可以使用环境变量来配置:
```bash
export QWEN_API_KEY="sk-your-actual-qwen-api-key"
export QWEN_MODEL="qwen-turbo"
```
#### 5. 其他模型支持
项目也支持其他LLM提供商
- **OpenAI**: GPT-3.5/GPT-4
- **Anthropic**: Claude系列
- **本地模型**: Ollama等
#### 6. 配置验证
启动系统后可以在Agent管理页面查看LLM使用统计确认配置是否正确。
## 📱 飞书集成配置
### 配置文件说明
`integrations_config.json` 文件包含飞书集成的所有配置信息:
```json
{
"feishu": {
"app_id": "cli_a8b50ec0eed1500d",
"app_secret": "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK",
"app_token": "XXnEbiCmEaMblSs6FDJcFCqsnIg",
"table_id": "tblnl3vJPpgMTSiP",
"last_updated": "2025-09-19T18:27:40.579958",
"status": "active"
},
"system": {
"sync_limit": 10,
"ai_suggestions_enabled": true,
"auto_sync_interval": 0,
"last_sync_time": null
}
}
```
### 配置参数说明
#### 飞书应用配置
- `app_id`: 飞书应用ID
- `app_secret`: 飞书应用密钥
- `app_token`: 飞书多维表格应用Token
- `table_id`: 飞书多维表格ID
- `last_updated`: 最后更新时间
- `status`: 集成状态active/inactive
#### 系统配置
- `sync_limit`: 同步记录数量限制
- `ai_suggestions_enabled`: 是否启用AI建议
- `auto_sync_interval`: 自动同步间隔(分钟)
- `last_sync_time`: 最后同步时间
### 获取飞书配置
1. **获取应用凭证**
- 访问 [飞书开放平台](https://open.feishu.cn/)
- 创建企业自建应用
- 获取 `app_id``app_secret`
2. **获取表格信息**
- 打开飞书多维表格
- 从URL中提取 `app_token``table_id`
- 例如:`https://my-ichery.feishu.cn/base/XXnEbiCmEaMblSs6FDJcFCqsnIg?table=tblnl3vJPpgMTSiP`
- `app_token`: `XXnEbiCmEaMblSs6FDJcFCqsnIg`
- `table_id`: `tblnl3vJPpgMTSiP`
3. **配置权限**
- 在飞书开放平台中配置应用权限
- 确保应用有读取多维表格的权限
### 字段映射配置
系统会自动映射以下飞书字段到本地数据库:
| 飞书字段 | 本地字段 | 类型 | 说明 |
|---------|---------|------|------|
| TR Number | order_id | String | 工单编号 |
| TR Description | description | Text | 工单描述 |
| Type of problem | category | String | 问题类型 |
| TR Level | priority | String | 优先级 |
| TR Status | status | String | 工单状态 |
| Source | source | String | 来源 |
| Created by | created_by | String | 创建人 |
| Module模块 | module | String | 模块 |
| Wilfulness责任人 | wilfulness | String | 责任人 |
| Date of close TR | date_of_close | DateTime | 关闭日期 |
| Vehicle Type01 | vehicle_type | String | 车型 |
| VIN\|sim | vin_sim | String | 车架号/SIM |
| App remote control version | app_remote_control_version | String | 应用远程控制版本 |
| HMI SW | hmi_sw | String | HMI软件版本 |
| 父记录 | parent_record | String | 父记录 |
| Has it been updated on the same day | has_updated_same_day | String | 是否同日更新 |
| Operating time | operating_time | String | 操作时间 |
## 🔧 配置管理
### 配置文件位置
- `llm_config.py`: LLM客户端配置
- `integrations_config.json`: 集成服务配置
- `integrations_config copy.json`: 配置备份文件
### 配置更新
- 修改配置文件后需要重启服务
- 建议在修改前备份配置文件
- 可以通过Web界面进行部分配置的在线修改
### 环境变量支持
系统支持通过环境变量覆盖配置文件设置:
```bash
# LLM配置
export LLM_PROVIDER="openai"
export LLM_API_KEY="your-api-key"
export LLM_MODEL="gpt-3.5-turbo"
# 飞书配置
export FEISHU_APP_ID="your-app-id"
export FEISHU_APP_SECRET="your-app-secret"
export FEISHU_APP_TOKEN="your-app-token"
export FEISHU_TABLE_ID="your-table-id"
```
## 🚨 注意事项
1. **安全性**: 配置文件包含敏感信息,请勿提交到版本控制系统
2. **备份**: 修改配置前请备份原文件
3. **权限**: 确保飞书应用有足够的权限访问多维表格
4. **测试**: 配置完成后建议先进行测试同步
5. **监控**: 定期检查同步状态和错误日志

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
AI准确率配置
管理AI建议的准确率阈值和相关配置
"""
from dataclasses import dataclass
from typing import Dict, Any
@dataclass
class AIAccuracyConfig:
"""AI准确率配置类"""
# 相似度阈值配置
auto_approve_threshold: float = 0.95 # 自动审批阈值≥95%
use_human_resolution_threshold: float = 0.90 # 使用人工描述阈值(<90%
manual_review_threshold: float = 0.80 # 人工审核阈值≥80%
# 置信度配置
ai_suggestion_confidence: float = 0.95 # AI建议默认置信度
human_resolution_confidence: float = 0.90 # 人工描述置信度
# 入库策略配置
prefer_human_when_low_accuracy: bool = True # 当AI准确率低时优先使用人工描述
enable_auto_approval: bool = True # 是否启用自动审批
enable_human_fallback: bool = True # 是否启用人工描述回退
def get_threshold_explanation(self, similarity: float) -> str:
"""获取相似度阈值的解释"""
if similarity >= self.auto_approve_threshold:
return f"相似度≥{self.auto_approve_threshold*100:.0f}%自动审批使用AI建议"
elif similarity >= self.manual_review_threshold:
return f"相似度≥{self.manual_review_threshold*100:.0f}%,建议人工审核"
elif similarity >= self.use_human_resolution_threshold:
return f"相似度<{self.use_human_resolution_threshold*100:.0f}%,建议使用人工描述"
else:
return f"相似度<{self.use_human_resolution_threshold*100:.0f}%,优先使用人工描述"
def should_use_human_resolution(self, similarity: float) -> bool:
"""判断是否应该使用人工描述"""
return similarity < self.use_human_resolution_threshold
def should_auto_approve(self, similarity: float) -> bool:
"""判断是否应该自动审批"""
return similarity >= self.auto_approve_threshold and self.enable_auto_approval
def get_confidence_score(self, similarity: float, use_human: bool = False) -> float:
"""获取置信度分数"""
if use_human:
return self.human_resolution_confidence
else:
return max(similarity, self.ai_suggestion_confidence)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
return {
"auto_approve_threshold": self.auto_approve_threshold,
"use_human_resolution_threshold": self.use_human_resolution_threshold,
"manual_review_threshold": self.manual_review_threshold,
"ai_suggestion_confidence": self.ai_suggestion_confidence,
"human_resolution_confidence": self.human_resolution_confidence,
"prefer_human_when_low_accuracy": self.prefer_human_when_low_accuracy,
"enable_auto_approval": self.enable_auto_approval,
"enable_human_fallback": self.enable_human_fallback
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AIAccuracyConfig':
"""从字典创建配置"""
return cls(**data)
# 默认配置实例
DEFAULT_CONFIG = AIAccuracyConfig()
# 配置预设
PRESETS = {
"conservative": AIAccuracyConfig(
auto_approve_threshold=0.98,
use_human_resolution_threshold=0.85,
manual_review_threshold=0.90,
human_resolution_confidence=0.95
),
"balanced": AIAccuracyConfig(
auto_approve_threshold=0.95,
use_human_resolution_threshold=0.90,
manual_review_threshold=0.80,
human_resolution_confidence=0.90
),
"aggressive": AIAccuracyConfig(
auto_approve_threshold=0.90,
use_human_resolution_threshold=0.80,
manual_review_threshold=0.70,
human_resolution_confidence=0.85
)
}
def get_accuracy_config(preset: str = "balanced") -> AIAccuracyConfig:
"""获取准确率配置"""
return PRESETS.get(preset, DEFAULT_CONFIG)
def update_accuracy_config(config: AIAccuracyConfig) -> bool:
"""更新准确率配置(可以保存到文件或数据库)"""
try:
# 这里可以实现配置的持久化存储
# 例如保存到配置文件或数据库
return True
except Exception:
return False

View File

@@ -0,0 +1,16 @@
{
"feishu": {
"app_id": "tblnl3vJPpgMTSiP",
"app_secret": "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK",
"app_token": "XXnEbiCmEaMblSs6FDJcFCqsnlg",
"table_id": "tblnl3vJPpgMTSiP",
"last_updated": null,
"status": "inactive"
},
"system": {
"sync_limit": 10,
"ai_suggestions_enabled": true,
"auto_sync_interval": 0,
"last_sync_time": null
}
}

View File

@@ -1,9 +1,11 @@
{
"feishu": {
"app_id": "cli_a8b50ec0eed1500d",
"app_secret": "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK",
"app_token": "XXnEbiCmEaMblSs6FDJcFCqsnIg",
"table_id": "tblnl3vJPpgMTSiP",
"status": "active",
"app_secret": "***"
"last_updated": "2025-09-19T18:40:55.291113",
"status": "active"
},
"system": {
"sync_limit": 10,

60
config/llm_config.py Normal file
View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""
LLM配置文件 - 千问模型配置
"""
from src.agent.llm_client import LLMConfig
# 千问模型配置
QWEN_CONFIG = LLMConfig(
provider="qwen",
api_key="sk-c0dbefa1718d46eaa897199135066f00", # 请替换为您的千问API密钥
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus-latest", # 可选: qwen-turbo, qwen-plus, qwen-max
temperature=0.7,
max_tokens=2000
)
# 其他模型配置示例
OPENAI_CONFIG = LLMConfig(
provider="openai",
api_key="sk-your-openai-api-key-here",
model="gpt-3.5-turbo",
temperature=0.7,
max_tokens=2000
)
ANTHROPIC_CONFIG = LLMConfig(
provider="anthropic",
api_key="sk-ant-your-anthropic-api-key-here",
model="claude-3-sonnet-20240229",
temperature=0.7,
max_tokens=2000
)
# 默认使用千问模型
DEFAULT_CONFIG = QWEN_CONFIG
def get_default_llm_config() -> LLMConfig:
"""
获取默认的LLM配置
优先从统一配置管理器获取,如果失败则使用本地配置
"""
try:
from src.config.unified_config import get_config
config = get_config()
llm_dict = config.get_llm_config()
# 创建LLMConfig对象
return LLMConfig(
provider=llm_dict.get("provider", "qwen"),
api_key=llm_dict.get("api_key", ""),
base_url=llm_dict.get("base_url", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
model=llm_dict.get("model", "qwen-plus-latest"),
temperature=llm_dict.get("temperature", 0.7),
max_tokens=llm_dict.get("max_tokens", 2000)
)
except Exception:
# 如果统一配置不可用,使用本地配置
return DEFAULT_CONFIG

View File

@@ -0,0 +1,52 @@
{
"database": {
"url": "mysql+pymysql://tsp_assistant:password@jeason.online/tsp_assistant?charset=utf8mb4",
"pool_size": 10,
"max_overflow": 20,
"pool_timeout": 30,
"pool_recycle": 3600
},
"llm": {
"provider": "qwen",
"api_key": "sk-c0dbefa1718d46eaa897199135066f00",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"model": "qwen-plus-latest",
"temperature": 0.7,
"max_tokens": 2000,
"timeout": 30
},
"server": {
"host": "0.0.0.0",
"port": 5000,
"websocket_port": 8765,
"debug": false,
"log_level": "INFO"
},
"feishu": {
"app_id": "",
"app_secret": "",
"app_token": "",
"table_id": "",
"status": "active",
"sync_limit": 10,
"auto_sync_interval": 0
},
"ai_accuracy": {
"auto_approve_threshold": 0.95,
"use_human_resolution_threshold": 0.9,
"manual_review_threshold": 0.8,
"ai_suggestion_confidence": 0.95,
"human_resolution_confidence": 0.9,
"prefer_human_when_low_accuracy": true,
"enable_auto_approval": true,
"enable_human_fallback": true
},
"system": {
"backup_enabled": true,
"backup_interval": 24,
"max_backup_files": 7,
"cache_enabled": true,
"cache_ttl": 3600,
"monitoring_enabled": true
}
}

View File

@@ -1,672 +0,0 @@
################################################################################
# DEPRECATED CONFIGURATION FILE: config/llm_config.py
################################################################################
# -*- coding: utf-8 -*-
"""
LLM配置文件 - 千问模型配置
"""
from src.agent.llm_client import LLMConfig
# 千问模型配置
QWEN_CONFIG = LLMConfig(
provider="qwen",
api_key="sk-c0dbefa1718d46eaa897199135066f00", # 请替换为您的千问API密钥
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus-latest", # 可选: qwen-turbo, qwen-plus, qwen-max
temperature=0.7,
max_tokens=2000
)
# 其他模型配置示例
OPENAI_CONFIG = LLMConfig(
provider="openai",
api_key="sk-your-openai-api-key-here",
model="gpt-3.5-turbo",
temperature=0.7,
max_tokens=2000
)
ANTHROPIC_CONFIG = LLMConfig(
provider="anthropic",
api_key="sk-ant-your-anthropic-api-key-here",
model="claude-3-sonnet-20240229",
temperature=0.7,
max_tokens=2000
)
# 默认使用千问模型
DEFAULT_CONFIG = QWEN_CONFIG
def get_default_llm_config() -> LLMConfig:
"""
获取默认的LLM配置
优先从统一配置管理器获取,如果失败则使用本地配置
"""
try:
from src.config.unified_config import get_config
config = get_config()
llm_dict = config.get_llm_config()
# 创建LLMConfig对象
return LLMConfig(
provider=llm_dict.get("provider", "qwen"),
api_key=llm_dict.get("api_key", ""),
base_url=llm_dict.get("base_url", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
model=llm_dict.get("model", "qwen-plus-latest"),
temperature=llm_dict.get("temperature", 0.7),
max_tokens=llm_dict.get("max_tokens", 2000)
)
except Exception:
# 如果统一配置不可用,使用本地配置
return DEFAULT_CONFIG
################################################################################
# DEPRECATED CONFIGURATION FILE: config/integrations_config.json
################################################################################
{
"feishu": {
"app_id": "cli_a8b50ec0eed1500d",
"app_secret": "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK",
"app_token": "XXnEbiCmEaMblSs6FDJcFCqsnIg",
"table_id": "tblnl3vJPpgMTSiP",
"last_updated": "2025-09-19T18:40:55.291113",
"status": "active"
},
"system": {
"sync_limit": 10,
"ai_suggestions_enabled": true,
"auto_sync_interval": 0,
"last_sync_time": null
}
}
################################################################################
# DEPRECATED CONFIGURATION FILE: config/ai_accuracy_config.py
################################################################################
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
AI准确率配置
管理AI建议的准确率阈值和相关配置
"""
from dataclasses import dataclass
from typing import Dict, Any
@dataclass
class AIAccuracyConfig:
"""AI准确率配置类"""
# 相似度阈值配置
auto_approve_threshold: float = 0.95 # 自动审批阈值≥95%
use_human_resolution_threshold: float = 0.90 # 使用人工描述阈值(<90%
manual_review_threshold: float = 0.80 # 人工审核阈值≥80%
# 置信度配置
ai_suggestion_confidence: float = 0.95 # AI建议默认置信度
human_resolution_confidence: float = 0.90 # 人工描述置信度
# 入库策略配置
prefer_human_when_low_accuracy: bool = True # 当AI准确率低时优先使用人工描述
enable_auto_approval: bool = True # 是否启用自动审批
enable_human_fallback: bool = True # 是否启用人工描述回退
def get_threshold_explanation(self, similarity: float) -> str:
"""获取相似度阈值的解释"""
if similarity >= self.auto_approve_threshold:
return f"相似度≥{self.auto_approve_threshold*100:.0f}%自动审批使用AI建议"
elif similarity >= self.manual_review_threshold:
return f"相似度≥{self.manual_review_threshold*100:.0f}%,建议人工审核"
elif similarity >= self.use_human_resolution_threshold:
return f"相似度<{self.use_human_resolution_threshold*100:.0f}%,建议使用人工描述"
else:
return f"相似度<{self.use_human_resolution_threshold*100:.0f}%,优先使用人工描述"
def should_use_human_resolution(self, similarity: float) -> bool:
"""判断是否应该使用人工描述"""
return similarity < self.use_human_resolution_threshold
def should_auto_approve(self, similarity: float) -> bool:
"""判断是否应该自动审批"""
return similarity >= self.auto_approve_threshold and self.enable_auto_approval
def get_confidence_score(self, similarity: float, use_human: bool = False) -> float:
"""获取置信度分数"""
if use_human:
return self.human_resolution_confidence
else:
return max(similarity, self.ai_suggestion_confidence)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
return {
"auto_approve_threshold": self.auto_approve_threshold,
"use_human_resolution_threshold": self.use_human_resolution_threshold,
"manual_review_threshold": self.manual_review_threshold,
"ai_suggestion_confidence": self.ai_suggestion_confidence,
"human_resolution_confidence": self.human_resolution_confidence,
"prefer_human_when_low_accuracy": self.prefer_human_when_low_accuracy,
"enable_auto_approval": self.enable_auto_approval,
"enable_human_fallback": self.enable_human_fallback
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AIAccuracyConfig':
"""从字典创建配置"""
return cls(**data)
# 默认配置实例
DEFAULT_CONFIG = AIAccuracyConfig()
# 配置预设
PRESETS = {
"conservative": AIAccuracyConfig(
auto_approve_threshold=0.98,
use_human_resolution_threshold=0.85,
manual_review_threshold=0.90,
human_resolution_confidence=0.95
),
"balanced": AIAccuracyConfig(
auto_approve_threshold=0.95,
use_human_resolution_threshold=0.90,
manual_review_threshold=0.80,
human_resolution_confidence=0.90
),
"aggressive": AIAccuracyConfig(
auto_approve_threshold=0.90,
use_human_resolution_threshold=0.80,
manual_review_threshold=0.70,
human_resolution_confidence=0.85
)
}
def get_accuracy_config(preset: str = "balanced") -> AIAccuracyConfig:
"""获取准确率配置"""
return PRESETS.get(preset, DEFAULT_CONFIG)
def update_accuracy_config(config: AIAccuracyConfig) -> bool:
"""更新准确率配置(可以保存到文件或数据库)"""
try:
# 这里可以实现配置的持久化存储
# 例如保存到配置文件或数据库
return True
except Exception:
return False
################################################################################
# DEPRECATED CONFIGURATION FILE: config/field_mapping_config.json
################################################################################
{
"field_mapping": {
"TR Number": "order_id",
"TR Description": "description",
"Type of problem": "category",
"TR Level": "priority",
"TR Status": "status",
"Source": "source",
"Date creation": "created_at",
"处理过程": "resolution",
"TR tracking": "resolution",
"Created by": "created_by",
"Module模块": "module",
"Wilfulness责任人": "wilfulness",
"Date of close TR": "date_of_close",
"Vehicle Type01": "vehicle_type",
"VIN|sim": "vin_sim",
"App remote control version": "app_remote_control_version",
"HMI SW": "hmi_sw",
"父记录": "parent_record",
"Has it been updated on the same day": "has_updated_same_day",
"Operating time": "operating_time",
"AI建议": "ai_suggestion",
"Issue Start Time": "updated_at",
"Wilfulness责任人<E4BBBB>?": "wilfulness",
"父<>?<3F>录": "parent_record",
"AI建<49>??": "ai_suggestion"
},
"field_aliases": {
"order_id": [
"TR Number",
"TR编号",
"工单号",
"Order ID",
"Ticket ID",
"工单编号",
"新字段1",
"新字段"
],
"description": [
"TR Description",
"TR描述",
"描述",
"Description",
"问题描述",
"详细描述"
],
"category": [
"Type of problem",
"问题类型",
"Category",
"分类",
"Problem Type",
"问题分类"
],
"priority": [
"TR Level",
"优先级",
"Priority",
"Level",
"紧急程度",
"重要程度"
],
"status": [
"TR Status",
"状态",
"Status",
"工单状态",
"处理状态"
],
"source": [
"Source",
"来源",
"Source Type",
"来源类型",
"提交来源"
],
"created_at": [
"Date creation",
"创建日期",
"Created At",
"Creation Date",
"创建时间"
],
"solution": [
"处理过程",
"Solution",
"解决方案",
"Process",
"处理方案"
],
"resolution": [
"TR tracking",
"Resolution",
"解决结果",
"跟踪",
"处理结果"
],
"created_by": [
"Created by",
"创建人",
"Creator",
"Created By",
"提交人"
],
"vehicle_type": [
"Vehicle Type01",
"车型",
"Vehicle Type",
"车辆类型",
"车款"
],
"vin_sim": [
"VIN|sim",
"VIN",
"车架号",
"SIM",
"VIN/SIM",
"车辆识别号"
],
"module": [
"Module模块",
"模块",
"Module",
"功能模块"
],
"wilfulness": [
"Wilfulness责任人",
"责任人",
"负责人",
"Assignee"
],
"date_of_close": [
"Date of close TR",
"关闭日期",
"Close Date",
"完成日期"
],
"app_remote_control_version": [
"App remote control version",
"应用远程控制版本",
"App Version",
"应用版本"
],
"hmi_sw": [
"HMI SW",
"HMI软件版本",
"HMI Software",
"人机界面软件"
],
"parent_record": [
"父记录",
"Parent Record",
"上级记录",
"关联记录"
],
"has_updated_same_day": [
"Has it been updated on the same day",
"是否同日更新",
"Same Day Update",
"当日更新"
],
"operating_time": [
"Operating time",
"操作时间",
"Operation Time",
"运行时间"
],
"ai_suggestion": [
"AI建议",
"AI Suggestion",
"AI建议",
"智能建议"
],
"updated_at": [
"Issue Start Time",
"问题开始时间",
"Start Time",
"更新时间"
]
},
"field_patterns": {
"order_id": [
".*number.*",
".*id.*",
".*编号.*",
".*ticket.*",
".*新.*"
],
"description": [
".*description.*",
".*描述.*",
".*detail.*",
".*内容.*"
],
"category": [
".*type.*",
".*category.*",
".*分类.*",
".*类型.*",
".*problem.*"
],
"priority": [
".*level.*",
".*priority.*",
".*优先级.*",
".*urgent.*"
],
"status": [
".*status.*",
".*状态.*",
".*state.*"
],
"source": [
".*source.*",
".*来源.*",
".*origin.*"
],
"created_at": [
".*creation.*",
".*created.*",
".*创建.*",
".*date.*"
],
"solution": [
".*solution.*",
".*处理.*",
".*解决.*",
".*process.*"
],
"resolution": [
".*resolution.*",
".*tracking.*",
".*跟踪.*",
".*result.*"
],
"created_by": [
".*created.*by.*",
".*creator.*",
".*创建人.*",
".*author.*"
],
"vehicle_type": [
".*vehicle.*type.*",
".*车型.*",
".*车辆.*",
".*car.*"
],
"vin_sim": [
".*vin.*",
".*sim.*",
".*车架.*",
".*识别.*"
],
"module": [
".*module.*",
".*模块.*",
".*功能.*"
],
"wilfulness": [
".*wilfulness.*",
".*责任人.*",
".*负责人.*",
".*assignee.*"
],
"date_of_close": [
".*close.*",
".*关闭.*",
".*完成.*",
".*finish.*"
],
"app_remote_control_version": [
".*app.*version.*",
".*应用.*版本.*",
".*remote.*control.*"
],
"hmi_sw": [
".*hmi.*",
".*软件.*",
".*software.*"
],
"parent_record": [
".*parent.*",
".*父.*",
".*上级.*",
".*关联.*"
],
"has_updated_same_day": [
".*same.*day.*",
".*同日.*",
".*当日.*",
".*updated.*same.*"
],
"operating_time": [
".*operating.*time.*",
".*操作.*时间.*",
".*运行.*时间.*"
],
"ai_suggestion": [
".*ai.*suggestion.*",
".*ai.*建议.*",
".*智能.*建议.*"
],
"updated_at": [
".*start.*time.*",
".*开始.*时间.*",
".*updated.*at.*",
".*更新时间.*"
]
},
"field_priorities": {
"order_id": 3,
"description": 3,
"category": 3,
"priority": 3,
"status": 3,
"created_at": 3,
"source": 3,
"solution": 3,
"resolution": 3,
"created_by": 3,
"vehicle_type": 3,
"vin_sim": 3,
"module": 3,
"wilfulness": 3,
"date_of_close": 3,
"app_remote_control_version": 3,
"hmi_sw": 3,
"parent_record": 3,
"has_updated_same_day": 3,
"operating_time": 3,
"ai_suggestion": 3,
"updated_at": 3
},
"auto_mapping_enabled": true,
"similarity_threshold": 0.6
}
################################################################################
# DEPRECATED CONFIGURATION FILE: config/unified_config.json
################################################################################
{
"database": {
"url": "mysql+pymysql://tsp_assistant:password@jeason.online/tsp_assistant?charset=utf8mb4",
"pool_size": 10,
"max_overflow": 20,
"pool_timeout": 30,
"pool_recycle": 3600
},
"llm": {
"provider": "qwen",
"api_key": "sk-c0dbefa1718d46eaa897199135066f00",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"model": "qwen-plus-latest",
"temperature": 0.7,
"max_tokens": 2000,
"timeout": 30
},
"server": {
"host": "0.0.0.0",
"port": 5000,
"websocket_port": 8765,
"debug": false,
"log_level": "INFO"
},
"feishu": {
"app_id": "",
"app_secret": "",
"app_token": "",
"table_id": "",
"status": "active",
"sync_limit": 10,
"auto_sync_interval": 0
},
"ai_accuracy": {
"auto_approve_threshold": 0.95,
"use_human_resolution_threshold": 0.9,
"manual_review_threshold": 0.8,
"ai_suggestion_confidence": 0.95,
"human_resolution_confidence": 0.9,
"prefer_human_when_low_accuracy": true,
"enable_auto_approval": true,
"enable_human_fallback": true
},
"system": {
"backup_enabled": true,
"backup_interval": 24,
"max_backup_files": 7,
"cache_enabled": true,
"cache_ttl": 3600,
"monitoring_enabled": true
}
}
################################################################################
# DEPRECATED CONFIGURATION FILE: src/config/config.py
################################################################################
import os
from typing import Dict, Any
class Config:
"""系统配置类"""
# 阿里云千问API配置
ALIBABA_API_KEY = "sk-c0dbefa1718d46eaa897199135066f00"
ALIBABA_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
ALIBABA_MODEL_NAME = "qwen-plus-latest"
# 数据库配置
DATABASE_URL = "mysql+pymysql://tsp_assistant:123456@jeason.online/tsp_assistant?charset=utf8mb4"
# DATABASE_URL = "sqlite:///local_test.db" # 本地测试数据库
# 知识库配置
KNOWLEDGE_BASE_PATH = "data/knowledge_base"
VECTOR_DB_PATH = "data/vector_db"
# 对话配置
MAX_HISTORY_LENGTH = 10
RESPONSE_TIMEOUT = 30
# 分析配置
ANALYTICS_UPDATE_INTERVAL = 3600 # 1小时
ALERT_THRESHOLD = 0.8 # 预警阈值
# 日志配置
LOG_LEVEL = "INFO"
LOG_FILE = "logs/tsp_assistant.log"
# 系统监控配置
SYSTEM_MONITORING = True # 是否启用系统监控
MONITORING_INTERVAL = 60 # 监控间隔(秒)
@classmethod
def get_api_config(cls) -> Dict[str, Any]:
"""获取API配置"""
return {
"api_key": cls.ALIBABA_API_KEY,
"base_url": cls.ALIBABA_BASE_URL,
"model_name": cls.ALIBABA_MODEL_NAME
}
@classmethod
def get_database_config(cls) -> Dict[str, Any]:
"""获取数据库配置"""
return {
"url": cls.DATABASE_URL,
"echo": False
}
@classmethod
def get_knowledge_config(cls) -> Dict[str, Any]:
"""获取知识库配置"""
return {
"base_path": cls.KNOWLEDGE_BASE_PATH,
"vector_db_path": cls.VECTOR_DB_PATH
}
@classmethod
def get_config(cls) -> Dict[str, Any]:
"""获取完整配置"""
return {
"system_monitoring": cls.SYSTEM_MONITORING,
"monitoring_interval": cls.MONITORING_INTERVAL,
"log_level": cls.LOG_LEVEL,
"log_file": cls.LOG_FILE,
"analytics_update_interval": cls.ANALYTICS_UPDATE_INTERVAL,
"alert_threshold": cls.ALERT_THRESHOLD
}

View File

@@ -1,37 +1,16 @@
{
"api_timeout": 30,
"max_history": 10,
"refresh_interval": 10,
"auto_monitoring": true,
"agent_mode": true,
"api_provider": "openai",
"api_base_url": "",
"api_key": "",
"api_provider": "openai",
"api_timeout": 30,
"auto_monitoring": true,
"cpu_usage_percent": 0,
"current_server_port": 5000,
"current_websocket_port": 8765,
"log_level": "INFO",
"max_history": 10,
"memory_usage_percent": 70.9,
"model_max_tokens": 1000,
"model_name": "qwen-turbo",
"model_temperature": 0.7,
"refresh_interval": 10,
"model_max_tokens": 1000,
"server_port": 5000,
"uptime_seconds": 0,
"websocket_port": 8765,
"modules": {
"dashboard": true,
"chat": true,
"knowledge": true,
"workorders": true,
"conversation-history": true,
"alerts": true,
"feishu-sync": true,
"agent": false,
"token-monitor": true,
"ai-monitor": true,
"analytics": true,
"system-optimizer": true,
"settings": true,
"tenant-management": true
}
"log_level": "INFO"
}

Binary file not shown.

View File

@@ -0,0 +1,9 @@
{
"init_time": "2025-12-12T13:31:25.459591",
"database_version": "SQLite 3.43.1",
"database_url": "sqlite:///tsp_assistant.db",
"migrations_applied": 0,
"tables_created": 9,
"initial_data_inserted": true,
"verification_passed": true
}

View File

@@ -1,395 +0,0 @@
# 飞书长连接模式使用指南
> **已验证可用** - 2026-02-11
>
> 本文档介绍如何使用飞书官方 SDK 的长连接模式,无需公网域名即可接入飞书机器人。
## 🎯 什么是长连接模式?
飞书官方 SDK 提供了**事件订阅 2.0(长连接模式)**,相比传统的 webhook 模式有以下优势:
| 特性 | Webhook 模式(旧) | 长连接模式(新) |
|------|-------------------|-------------------|
| 公网域名 | 必需 | 不需要 |
| SSL 证书 | 必需 | 不需要 |
| 回调配置 | 需要配置 | 不需要 |
| 内网部署 | 不支持 | 支持 |
| 实时性 | 中等HTTP 轮询) | 高WebSocket |
| 稳定性 | 中等 | 高(自动重连) |
---
## 📦 安装步骤
### 1. 安装依赖
```bash
cd /Users/macos/Desktop/tsp-assist/assist
pip install -r requirements.txt
```
**核心依赖:**
- `lark-oapi==1.3.5` - 飞书官方 Python SDK
### 2. 配置环境变量
编辑 `.env` 文件:
```bash
# 飞书配置(必需)
FEISHU_APP_ID=cli_xxxxxxxxxxxxx # 你的飞书应用 ID
FEISHU_APP_SECRET=xxxxxxxxxxxxx # 你的飞书应用密钥
# 数据库配置
DATABASE_URL=sqlite:///./data/tsp_assistant.db
# LLM 配置
LLM_PROVIDER=qwen
LLM_API_KEY=sk-xxxxxxxxxxxxx
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
LLM_MODEL=qwen-plus-latest
```
### 3. 初始化数据库
```bash
python init_database.py
```
### 4. 在飞书开放平台配置应用权限
访问 [飞书开放平台](https://open.feishu.cn/app),为您的应用添加以下权限:
**必需权限:**
- `im:message` - 获取与发送单聊、群组消息
- `im:message:send_as_bot` - 以应用的身份发消息
- `im:chat` - 获取群组信息
**可选权限(用于工单同步):**
- `bitable:app` - 查看、评论、编辑和管理多维表格
**注意:** 添加权限后需要重新发布应用版本。
---
## 🚀 启动服务
### 方式 1: 使用启动脚本(推荐)
```bash
python start_feishu_bot.py
```
启动后会看到:
```
================================================================================
🚀 TSP Assistant - 飞书长连接客户端
================================================================================
📋 配置信息:
- App ID: cli_xxxxxxxxxxxxx
- 数据库: sqlite:///./data/tsp_assistant.db
- LLM: qwen/qwen-plus-latest
- 日志级别: INFO
🔌 启动模式: 事件订阅 2.0(长连接)
优势:
- 无需公网域名
- 无需配置 webhook
- 自动重连
- 实时接收消息
💡 提示: 按 Ctrl+C 停止服务
================================================================================
🚀 启动飞书长连接客户端...
- App ID: cli_xxxxxxxxxxxxx
- 模式: 事件订阅 2.0(长连接)
- 无需公网域名和 webhook 配置
飞书长连接服务初始化成功
```
### 方式 2: 在代码中集成
如果您想将长连接服务集成到现有的 Flask 应用中:
```python
import threading
from src.integrations.feishu_longconn_service import get_feishu_longconn_service
# 在后台线程中启动长连接服务
def start_feishu_in_background():
service = get_feishu_longconn_service()
service.start()
# 启动
feishu_thread = threading.Thread(target=start_feishu_in_background, daemon=True)
feishu_thread.start()
# 然后启动 Flask 应用
app.run(...)
```
---
## 🧪 测试
### 1. 在飞书群聊中测试
**场景 1单个用户对话**
```
用户 A: @TSP助手 车辆无法连接网络怎么办?
机器人: [基于工单知识库的智能回复]
用户 A: 还有其他解决方法吗?
机器人: [基于上下文的多轮对话回复]
```
**场景 2多用户群聊隔离**
```
群聊 1:
用户 A: @TSP助手 我的车有问题
机器人: [针对用户 A 的回复]
用户 B: @TSP助手 我的车有问题
机器人: [针对用户 B 的独立回复,不受用户 A 影响]
群聊 2:
用户 A: @TSP助手 我的车有问题
机器人: [全新对话,不受群聊 1 影响]
```
### 2. 查看日志
服务运行时会实时输出日志:
```
[Feishu LongConn] 收到消息
- 消息ID: om_xxxxxxxxxxxxx
- 群聊ID: oc_xxxxxxxxxxxxx
- 发送者: ou_xxxxxxxxxxxxx
- 消息类型: text
- 原始内容: @TSP助手 车辆无法连接网络
- 清理后内容: 车辆无法连接网络
会话用户标识: feishu_oc_xxxxxxxxxxxxx_ou_xxxxxxxxxxxxx
为用户 ou_xxxxxxxxxxxxx 在群聊 oc_xxxxxxxxxxxxx 创建新会话: session_xxxxxxxxxxxxx
🤖 调用 TSP Assistant 处理消息...
准备发送回复 (长度: 156)
成功回复消息: om_xxxxxxxxxxxxx
```
---
## 🔧 与旧模式的对比
### 旧模式Webhook- 已废弃
**文件:** `src/web/blueprints/feishu_bot.py`
**工作流程:**
```
飞书 → 公网域名 → Webhook → Flask → 处理消息
```
**缺点:**
- 需要公网域名
- 需要配置 SSL
- 内网无法部署
- 需要在飞书后台配置回调地址
### 新模式(长连接)- 推荐
**文件:** `src/integrations/feishu_longconn_service.py`
**工作流程:**
```
飞书 ← WebSocket 长连接 ← SDK 客户端 ← 处理消息
```
**优点:**
- 无需公网域名
- 无需 SSL 证书
- 内网可部署
- 无需配置回调地址
- 自动重连
- 更稳定
---
## 架构说明
```
┌─────────────────────────────────────────────────────────────┐
│ 飞书服务器 │
│ (open.feishu.cn) │
└───────────────────────┬─────────────────────────────────────┘
│ WebSocket 长连接
│ (事件订阅 2.0)
┌─────────────────────────────────────────────────────────────┐
│ TSP Assistant (内网部署) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ feishu_longconn_service.py │ │
│ │ - 飞书 SDK 长连接客户端 │ │
│ │ - 自动接收消息事件 │ │
│ │ - 群聊隔离 (feishu_群ID_用户ID) │ │
│ └──────────────┬─────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ RealtimeChatManager │ │
│ │ - 会话管理 │ │
│ │ - 多轮对话 │ │
│ └──────────────┬─────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ TSP Assistant 智能引擎 │ │
│ │ - LLM 调用 │ │
│ │ - 工单知识库匹配 │ │
│ │ - AI 分析和建议 │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## ⚙️ 高级配置
### 1. 调整日志级别
编辑 `.env`
```bash
LOG_LEVEL=DEBUG # 查看详细日志
LOG_LEVEL=INFO # 默认(推荐)
LOG_LEVEL=WARNING # 仅警告和错误
```
### 2. 同时运行 Web 管理后台和长连接服务
**方式 1使用两个终端**
```bash
# 终端 1 - Web 管理后台
python start_dashboard.py
# 终端 2 - 飞书长连接服务
python start_feishu_bot.py
```
**方式 2使用进程管理器推荐生产环境**
创建 `supervisord.conf`
```ini
[program:tsp-web]
command=python start_dashboard.py
directory=/Users/macos/Desktop/tsp-assist/assist
autostart=true
autorestart=true
[program:tsp-feishu]
command=python start_feishu_bot.py
directory=/Users/macos/Desktop/tsp-assist/assist
autostart=true
autorestart=true
```
启动:
```bash
supervisord -c supervisord.conf
```
---
## ❓ 常见问题
### Q1: SSL 证书验证失败怎么办?
**错误信息:**
```
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain
```
**解决方案macOS**
1. **运行 Python 证书安装脚本**(推荐):
```bash
open "/Applications/Python 3.10/Install Certificates.command"
```
或者手动运行:
```bash
/Applications/Python\ 3.10/Install\ Certificates.command
```
2. **升级 certifi 包**
```bash
python3 -m pip install --upgrade certifi
```
3. **验证修复**
```bash
python3 -c "import urllib.request; urllib.request.urlopen('https://open.feishu.cn', timeout=5); print(' SSL 验证成功')"
```
**解决方案Linux/Windows**
```bash
pip3 install --upgrade certifi
```
### Q2: 启动后没有收到消息?
**检查清单:**
1. 确认飞书应用权限已配置(`im:message`
2. 确认应用已发布最新版本
3. 确认机器人已添加到群聊
4. 查看日志是否有连接成功的提示
5. 确认没有 SSL 证书错误(见 Q1
### Q3: 提示"权限不足"
**解决方案:**
1. 登录飞书开放平台
2. 进入应用管理 → 权限管理
3. 添加所需权限
4. 创建新版本并发布
5. 重启长连接服务
### Q4: 如何停止服务?
`Ctrl+C` 即可优雅停止。
### Q5: 可以在多台服务器上运行吗?
**不建议**。同一个飞书应用只应运行一个长连接客户端实例,否则会导致消息重复处理。
### Q6: 如何迁移旧的 Webhook 模式?
**建议:**
1. 保留旧代码(`feishu_bot.py`)作为备份
2. 使用新的长连接模式(`start_feishu_bot.py`
3. 测试无误后,可以移除 webhook 端点
---
## 🎉 总结
现在您的 TSP Assistant 已经支持**飞书官方 SDK 的长连接模式**
**核心优势:**
- 无需公网域名
- 无需 webhook 配置
- 内网可部署
- 自动重连
- 群聊隔离
- 完整的工单管理和 AI 分析功能
**使用方式:**
```bash
python start_feishu_bot.py
```
就这么简单!🚀

View File

@@ -1,51 +0,0 @@
麻烦客户确认下app设置定位和蓝牙权限是否是始终允许,以及检查下手机系统设置是否允许奇瑞app后台运行.
车辆处于深度睡眠中,不支持远控,建议客户用物理钥匙重启车辆后再试下、
车辆注册数据和卡状态正常,可能网络原因数据未及时同步,麻烦客户重启车辆到信号好的地方再观察下
车辆最后注册时间2026-01-23 15:03:47,远控提示网络超时,已踢卡, 麻烦客户重启车辆到信号好的地方刷新app再试下。如未恢复建议客户长按SOS按键11秒以上重启TBOX再试下
最后注册时间2026-01-25 08:22:11卡不在线远控提示网络超时麻烦t-box老师看下
车辆最后注册时间2025-07-30 17:57:01, 已踢卡,麻烦客户重启车辆到信号好的地方再试下
目前后台远控指令反馈成功,之前失败原因网络超时,麻烦回访客户再确认下
车辆最后注册时间 SIM卡已停用麻烦转辛巴处理
座椅通风在爱车界面,点击车辆图片进去,之后点击图片上的座椅进行远控
未查询到车辆信息,麻烦客户再确认下车架号
目前车门状态通过车辆图片车门颜色显示,红色表示车门打开解锁状态,蓝色表示上锁状态
麻烦客户手机设置中检查下定位GPS权限是否开启并授权奇瑞app
远控空调提示T-BOX认证失败麻烦t-box老师看下
已踢卡,麻烦客户重启车辆,退出账号重新登录试下
麻烦客户点击爱车界面的车辆图片进入详情页查看总里程数、电耗油耗等数据
该车型配置座椅加热通风功能,麻烦客户点击图片到详情页点图片上座椅位置控制
智云互联目前已不维护建议客户下载奇瑞app再试下
车辆注册数据和卡状态正常29日服务器数据有积压导致实时数据未更新目前已修复建议客户重启车辆到信号好的地方刷新app看数据是否更新
麻烦客户到设置里面关闭自动开空调的开关,然后先远控发动机启动后再远控空调试下;然后如果是开启了自动开空调开关,麻烦客户点击远控启动发动机,会自动开启空调
3.6.4版本高频问题
1. 更新3.6.4版本奇瑞APP后如果客户杀掉过APP重新打开APP后下发空调类远控会提示控制器反馈失败
解决方案建议客户进入APP后先爱车界面下拉刷新界面再下发远控空调指令
长期解决APP发版3.6.6版本该问题已修复)
2. 车控界面不显示流量和到流量期时间,只显示符号
解决方案建议客户在爱车界面下拉刷新APP正常可恢复
长期解决APP发版3.6.6版本该问题已修复)
3. 每次打开APP后查看行程记录都要重新点击确认开关
问题原因:合规要求,每次查看行程记录均需要点击确认
长期解决APP发版3.6.6版本该问题已修复)
4. 客户点击行程记录,开启开关后,行程记录不显示或者无法点击日期切换查看记录
解决方案建议客户在查看行程记录开启开关后回退爱车界面下拉几次刷新下APP然后再点击行程记录界面查看, 查询接口在优化中;
长期解决APP发版3.6.6版本该问题已修复)
5. T26PHEV风云T10车主远控预约充电失败
解决方案:建议客户先在车机上设置预约充电,该问题正在修复中;——
长期解决APP发版3.6.6版本该问题已修复)
6. 奇瑞APP查看车辆定位是正确的点击导航倒车路线不准确车辆位置不准
问题原因:因为地图导航坐标系改动涉及方面较多,目前仍在优化中, 预计下个版本会修复
长期解决APP发版等待3.6.8版本修复)
APP车控问题
E01车型风云A9LAPP车况数据不显示百公里电耗和油耗等待修复
E01车型暂不显示百公里耗油和耗电数据在优化中麻烦安抚客户先等待下
预约充电只充一个小时就自动断开停止了
TBOX问题建议客户进站升级TBOX
APP提示哨兵模式自动退出实际车辆未退出
麻烦客户在车机上先直接语音或者下拉负一屏来打开哨兵模式目前APP打开哨兵模式报异常退出的问题开发正在修复中
T26PHEV 更新到3.6.5后不显示电耗3.6.6版本已修复)
客户进线表示奇瑞汽车APP更新最新版本后只显示油耗不显示电耗APP版本号3.6.5
该问题存在于3.6.4和3.6.5版本切换新车控3.6.6版本已修复建议客户更新APP到3.6.6版本
APP空调不显示具体温度数值
外部车型中检查空调类型如是自动空调可以显示具体的温度数值类型为电动空调无温度数值显示只显示LO & HI

26
git_push.bat Normal file
View File

@@ -0,0 +1,26 @@
@echo off
rem 获取当前日期和时间
for /f "tokens=1-6 delims=/ " %%a in ('date /t') do set CDATE=%%a-%%b-%%c
for /f "tokens=1-2 delims=:\ " %%a in ('time /t') do set CTIME=%%a-%%b
set COMMIT_MESSAGE="feat: %CDATE% %CTIME% - 全面架构重构、功能增强及问题修复"
rem 添加所有变更到暂存区
git add .
rem 检查是否有文件被添加、修改或删除
git diff --cached --quiet
if %errorlevel% equ 0 (
echo 没有检测到需要提交的变更。
) else (
rem 提交变更
git commit -m %COMMIT_MESSAGE%
rem 推送变更到远程仓库
git push origin master
echo Git 推送完成!
)
pause

23
git_push.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# 获取当前日期和时间
COMMIT_DATE=$(date +"%Y-%m-%d %H:%M:%S")
# 添加所有变更到暂存区
git add .
# 检查是否有文件被添加、修改或删除
if git diff --cached --quiet; then
echo "没有检测到需要提交的变更。"
else
# 创建提交消息
COMMIT_MESSAGE="feat: ${COMMIT_DATE} - 全面架构重构、功能增强及问题修复"
# 提交变更
git commit -m "$COMMIT_MESSAGE"
# 推送变更到远程仓库
git push origin master
echo "Git 推送完成!"
fi

View File

@@ -15,12 +15,12 @@ from pathlib import Path
# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from src.config.unified_config import get_config
from src.config.config import Config
from src.utils.helpers import setup_logging
from src.core.database import db_manager
from src.core.models import (
Base, WorkOrder, KnowledgeEntry, Conversation, Analytics, Alert, VehicleData,
WorkOrderSuggestion, WorkOrderProcessHistory, User, ChatSession
WorkOrderSuggestion, WorkOrderProcessHistory, User
)
class DatabaseInitializer:
@@ -38,21 +38,6 @@ class DatabaseInitializer:
# 迁移历史记录
self.migration_history = []
def _mask_db_url(self, url: str) -> str:
"""隐藏数据库连接字符串中的敏感信息"""
try:
# 隐藏密码部分
if '@' in url and ':' in url:
parts = url.split('@')
if len(parts) == 2:
auth_part = parts[0]
if ':' in auth_part:
protocol_user = auth_part.rsplit(':', 1)[0]
return f"{protocol_user}:***@{parts[1]}"
return url
except:
return url
def _get_database_version(self) -> str:
"""获取数据库版本信息"""
try:
@@ -76,14 +61,13 @@ class DatabaseInitializer:
print("TSP智能助手数据库初始化系统")
print("=" * 80)
print(f"数据库类型: {self.db_version}")
print(f"连接地址: {self._mask_db_url(self.db_url)}")
print(f"连接地址: {self.db_url}")
print(f"初始化时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
try:
# 设置日志
config = get_config()
setup_logging(config.server.log_level, "logs/tsp_assistant.log")
setup_logging(Config.LOG_LEVEL, Config.LOG_FILE)
# 测试数据库连接
if not self._test_connection():
@@ -110,9 +94,6 @@ class DatabaseInitializer:
if not self._verify_database_integrity():
return False
# 初始化 Redis 缓存(如果启用)
self._initialize_redis_cache()
# 生成初始化报告
self._generate_init_report()
@@ -197,9 +178,7 @@ class DatabaseInitializer:
self._migrate_workorder_dispatch_fields,
self._migrate_workorder_process_history_table,
self._migrate_analytics_enhancements,
self._migrate_system_optimization_fields,
self._migrate_chat_sessions_table,
self._migrate_tenant_id_fields,
self._migrate_system_optimization_fields
]
success_count = 0
@@ -447,61 +426,6 @@ class DatabaseInitializer:
return success
def _migrate_chat_sessions_table(self) -> bool:
"""迁移:创建 chat_sessions 表并为 conversations 添加 session_id 字段"""
print(" 检查会话管理表...")
try:
inspector = inspect(db_manager.engine)
# 1. 创建 chat_sessions 表
if 'chat_sessions' not in inspector.get_table_names():
print(" 创建 chat_sessions 表...")
ChatSession.__table__.create(db_manager.engine, checkfirst=True)
print(" chat_sessions 表创建成功")
else:
print(" chat_sessions 表已存在")
# 2. 为 conversations 表添加 session_id 字段
if not self._column_exists('conversations', 'session_id'):
print(" 添加 conversations.session_id 字段...")
self._add_table_columns('conversations', [
('session_id', 'VARCHAR(100)')
])
print(" session_id 字段添加成功")
else:
print(" conversations.session_id 字段已存在")
return True
except Exception as e:
print(f" 会话管理表迁移失败: {e}")
return False
def _migrate_tenant_id_fields(self) -> bool:
"""迁移:为核心表添加 tenant_id 多租户字段"""
print(" 检查多租户 tenant_id 字段...")
tables = [
"work_orders", "chat_sessions", "conversations",
"knowledge_entries", "analytics", "alerts", "users",
]
try:
added = 0
for table in tables:
if not self._column_exists(table, 'tenant_id'):
print(f" 添加 {table}.tenant_id ...")
self._add_table_columns(table, [
('tenant_id', "VARCHAR(50) DEFAULT 'default' NOT NULL")
])
added += 1
else:
print(f" {table}.tenant_id 已存在")
print(f" tenant_id 迁移完成,新增 {added} 个表")
return True
except Exception as e:
print(f" tenant_id 迁移失败: {e}")
return False
def _add_table_columns(self, table_name: str, fields: List[tuple]) -> bool:
"""为表添加字段"""
try:
@@ -596,16 +520,7 @@ class DatabaseInitializer:
# 插入初始知识库数据
initial_data = self._get_initial_knowledge_data()
for data in initial_data:
# 只使用模型定义中存在的字段
entry = KnowledgeEntry(
question=data['question'],
answer=data['answer'],
category=data['category'],
confidence_score=data.get('confidence_score', 0.7),
is_verified=data.get('is_verified', True),
verified_by=data.get('verified_by', 'system'),
verified_at=data.get('verified_at', datetime.now())
)
entry = KnowledgeEntry(**data)
session.add(entry)
session.commit()
@@ -632,7 +547,9 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
},
{
"question": "账户被锁定了怎么办?",
@@ -641,7 +558,9 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
},
{
"question": "如何修改个人信息?",
@@ -650,7 +569,9 @@ class DatabaseInitializer:
"confidence_score": 0.7,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.7
},
{
"question": "支付失败怎么办?",
@@ -659,7 +580,9 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
},
{
"question": "如何申请退款?",
@@ -668,7 +591,9 @@ class DatabaseInitializer:
"confidence_score": 0.7,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.7
},
{
"question": "系统无法访问怎么办?",
@@ -677,7 +602,9 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
},
{
"question": "如何联系客服?",
@@ -686,7 +613,9 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
},
{
"question": "如何远程启动车辆?",
@@ -695,7 +624,9 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
},
{
"question": "APP显示车辆信息错误怎么办",
@@ -704,7 +635,9 @@ class DatabaseInitializer:
"confidence_score": 0.8,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.8
},
{
"question": "车辆无法远程启动的原因?",
@@ -713,7 +646,9 @@ class DatabaseInitializer:
"confidence_score": 0.9,
"is_verified": True,
"verified_by": "system",
"verified_at": datetime.now()
"verified_at": datetime.now(),
"search_frequency": 0,
"relevance_score": 0.9
}
]
@@ -752,14 +687,10 @@ class DatabaseInitializer:
entry.is_verified = True
entry.verified_by = "system_init"
entry.verified_at = datetime.now()
# 尝试设置优化字段(如果存在)
try:
if hasattr(entry, 'search_frequency') and entry.search_frequency is None:
entry.search_frequency = 0
if hasattr(entry, 'relevance_score') and entry.relevance_score is None:
entry.relevance_score = 0.7
except:
pass # 如果字段不存在,忽略
if not hasattr(entry, 'search_frequency'):
entry.search_frequency = 0
if not hasattr(entry, 'relevance_score'):
entry.relevance_score = 0.7
session.commit()
print(f" 成功验证 {len(unverified_entries)} 条知识库条目")
@@ -863,34 +794,6 @@ class DatabaseInitializer:
except Exception:
return 0
def _initialize_redis_cache(self):
"""初始化 Redis 缓存"""
print("\n检查 Redis 缓存配置...")
try:
from src.core.cache_manager import cache_manager
health = cache_manager.health_check()
if health["status"] == "healthy":
print(" ✓ Redis 缓存连接成功")
stats = cache_manager.get_stats()
print(f" - 当前连接数: {stats.get('connected_clients', 0)}")
print(f" - 内存使用: {stats.get('used_memory_human', '0B')}")
print(f" - 总键数: {stats.get('total_keys', 0)}")
elif health["status"] == "disabled":
print(" ⚠ Redis 缓存未启用(可选功能)")
else:
print(f" ⚠ Redis 缓存连接失败: {health.get('message', '未知错误')}")
print(" 提示: Redis 是可选功能,不影响系统正常运行")
except ImportError:
print(" ⚠ Redis 库未安装,缓存功能将被禁用")
print(" 提示: 运行 'pip install redis hiredis' 安装 Redis 支持")
except Exception as e:
print(f" ⚠ Redis 初始化失败: {e}")
print(" 提示: Redis 是可选功能,不影响系统正常运行")
def check_database_status(self) -> Dict[str, Any]:
"""检查数据库状态"""
print("\n" + "=" * 80)

BIN
local_test.db Normal file

Binary file not shown.

5992
logs/dashboard.log Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
2025-12-12 13:30:49,040 - src.core.database - ERROR - 数据库操作失败: 'search_frequency' is an invalid keyword argument for KnowledgeEntry
2025-12-12 13:31:25,399 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V001 - location
2025-12-12 13:31:25,404 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V001 - status
2025-12-12 13:31:25,409 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V001 - battery
2025-12-12 13:31:25,414 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V001 - engine
2025-12-12 13:31:25,419 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V002 - location
2025-12-12 13:31:25,424 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V002 - status
2025-12-12 13:31:25,429 - src.vehicle.vehicle_data_manager - INFO - 添加车辆数据成功: V002 - fault
2025-12-12 13:31:25,429 - src.vehicle.vehicle_data_manager - INFO - 示例车辆数据添加成功

Binary file not shown.

Binary file not shown.

View File

@@ -56,19 +56,6 @@ http {
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# SSE 流式接口 — 关闭缓冲,支持逐 token 推送
location /api/chat/message/stream {
proxy_pass http://tsp_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 120s;
chunked_transfer_encoding on;
}
# WebSocket代理
location /ws/ {

175
quick_push.bat Normal file
View File

@@ -0,0 +1,175 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo 🚀 TSP智能助手 - 快速推送
echo.
:: 检查Git状态
git status --porcelain >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Git未初始化或不在Git仓库中
pause
exit /b 1
)
:: 检查是否有参数
if "%1"=="" (
:: 智能生成提交信息
echo 📝 分析markdown文件并生成提交信息...
:: 检查是否有markdown文件修改
set md_files=
for /f "tokens=*" %%f in ('git diff --name-only --cached 2^>nul ^| findstr /i "\.md$"') do (
set md_files=!md_files! %%f
)
for /f "tokens=*" %%f in ('git diff --name-only 2^>nul ^| findstr /i "\.md$"') do (
set md_files=!md_files! %%f
)
set commit_msg=
if not "%md_files%"=="" (
echo 📄 检测到markdown文件修改: %md_files%
:: 提取markdown文件的主要内容
set commit_title=
set commit_type=docs
:: 检查是否有修复相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "修复\|解决\|问题\|错误"') do (
set commit_type=fix
set commit_title=修复问题
goto :found_fix
)
)
)
:: 检查是否有新功能相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "功能\|新增\|添加\|实现"') do (
set commit_type=feat
set commit_title=新增功能
goto :found_feature
)
)
)
:: 检查是否有优化相关内容
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "优化\|性能\|改进\|提升"') do (
set commit_type=perf
set commit_title=性能优化
goto :found_optimization
)
)
)
:: 提取文件标题
for %%f in (%md_files%) do (
if exist "%%f" (
for /f "tokens=*" %%l in ('type "%%f" ^| findstr /n "^#" ^| head -1') do (
set line=%%l
set line=!line:*:=!
set line=!line:# =!
set line=!line:## =!
if "!line!" neq "" (
set commit_title=!line!
goto :found_title
)
)
)
)
:found_fix
:found_feature
:found_optimization
:found_title
if "%commit_title%"=="" (
set commit_title=更新文档记录
)
:: 生成提交信息
set commit_msg=%commit_type%: %commit_title%
) else (
echo 没有检测到markdown文件修改
set commit_msg=feat: 快速提交 - %date% %time%
)
) else (
set commit_msg=%1
)
echo 📝 提交信息: %commit_msg%
echo.
:: 检查是否有更改需要提交(含未跟踪文件)
setlocal enabledelayedexpansion
git diff --quiet
set has_unstaged=%errorlevel%
git diff --cached --quiet
set has_staged=%errorlevel%
set has_untracked=0
for /f "delims=" %%f in ('git ls-files --others --exclude-standard') do set has_untracked=1
if %has_unstaged% equ 0 if %has_staged% equ 0 if %has_untracked% equ 0 (
echo 没有检测到任何更改,无需提交
echo.
echo ✅ 工作区干净,无需推送
pause
exit /b 0
)
:: 执行推送
echo.
echo 📤 开始推送流程...
echo 📝 提交信息: %commit_msg%
git add .
if %errorlevel% neq 0 (
echo ❌ 添加文件失败
pause
exit /b 1
)
git commit -m "%commit_msg%"
if %errorlevel% neq 0 (
echo ❌ 提交失败
pause
exit /b 1
)
git fetch origin main
git push origin main
if %errorlevel% equ 0 (
echo.
echo ✅ 推送完成!
echo 📊 最新提交:
git log --oneline -1
) else (
echo.
echo ❌ 推送失败,尝试自动解决...
echo 🔄 执行: git pull origin main --rebase
git pull origin main --rebase
if %errorlevel% equ 0 (
echo ✅ 重试推送...
git push origin main
if %errorlevel% equ 0 (
echo ✅ 推送成功!
echo 📊 最新提交:
git log --oneline -1
) else (
echo ❌ 重试推送失败,请手动处理
)
) else (
echo ❌ 自动rebase失败请手动处理冲突后重试
)
)
echo.
pause

View File

@@ -37,8 +37,6 @@ aiohttp==3.10.10
# asyncio是Python内置模块不需要安装
# Redis缓存
redis==5.0.1
hiredis==2.3.2
# 测试框架
pytest==8.3.3
@@ -65,10 +63,4 @@ httpx==0.27.2
# 数据验证
pydantic==2.9.2
marshmallow==3.23.3
# 飞书官方 SDK事件订阅 2.0 - 长连接模式)
lark-oapi==1.3.5
# 本地 Embedding 模型可选EMBEDDING_ENABLED=True 时需要)
# pip install sentence-transformers torch
marshmallow==3.23.3

Binary file not shown.

306
scripts/deploy.sh Normal file
View File

@@ -0,0 +1,306 @@
#!/bin/bash
# TSP智能助手部署脚本
# 支持多环境部署、版本管理、自动备份
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
log_info "检查系统依赖..."
# 检查Python
if ! command -v python3 &> /dev/null; then
log_error "Python3 未安装"
exit 1
fi
# 检查pip
if ! command -v pip3 &> /dev/null; then
log_error "pip3 未安装"
exit 1
fi
# 检查Git
if ! command -v git &> /dev/null; then
log_error "Git 未安装"
exit 1
fi
log_info "依赖检查完成"
}
# 创建虚拟环境
setup_venv() {
local venv_path=$1
log_info "创建虚拟环境: $venv_path"
if [ ! -d "$venv_path" ]; then
python3 -m venv "$venv_path"
fi
source "$venv_path/bin/activate"
pip install --upgrade pip
log_info "虚拟环境设置完成"
}
# 安装依赖
install_dependencies() {
log_info "安装Python依赖..."
pip install -r requirements.txt
log_info "依赖安装完成"
}
# 数据库迁移
run_migrations() {
log_info "运行数据库迁移..."
# 检查数据库文件
if [ ! -f "tsp_assistant.db" ]; then
log_info "初始化数据库..."
python init_database.py
fi
log_info "数据库迁移完成"
}
# 创建systemd服务文件
create_systemd_service() {
local service_name=$1
local app_path=$2
local service_file="/etc/systemd/system/${service_name}.service"
log_info "创建systemd服务文件: $service_file"
sudo tee "$service_file" > /dev/null <<EOF
[Unit]
Description=TSP智能助手服务
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=$app_path
Environment=PATH=$app_path/venv/bin
ExecStart=$app_path/venv/bin/python start_dashboard.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "$service_name"
log_info "systemd服务创建完成"
}
# 创建nginx配置
create_nginx_config() {
local domain=$1
local app_port=$2
local config_file="/etc/nginx/sites-available/tsp_assistant"
log_info "创建nginx配置: $config_file"
sudo tee "$config_file" > /dev/null <<EOF
server {
listen 80;
server_name $domain;
location / {
proxy_pass http://127.0.0.1:$app_port;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location /static {
alias $app_path/src/web/static;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
EOF
# 启用站点
sudo ln -sf "$config_file" /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
log_info "nginx配置完成"
}
# 主部署函数
deploy() {
local environment=${1:-production}
local domain=${2:-localhost}
local app_port=${3:-5000}
log_info "开始部署TSP智能助手到 $environment 环境"
# 设置部署路径
case $environment in
development)
DEPLOY_PATH="./dev_deploy"
SERVICE_NAME=""
;;
staging)
DEPLOY_PATH="/opt/tsp_assistant_staging"
SERVICE_NAME="tsp_assistant_staging"
;;
production)
DEPLOY_PATH="/opt/tsp_assistant"
SERVICE_NAME="tsp_assistant"
;;
*)
log_error "未知环境: $environment"
exit 1
;;
esac
# 检查依赖
check_dependencies
# 创建部署目录
log_info "创建部署目录: $DEPLOY_PATH"
sudo mkdir -p "$DEPLOY_PATH"
sudo chown $USER:$USER "$DEPLOY_PATH"
# 复制文件
log_info "复制应用文件..."
cp -r . "$DEPLOY_PATH/"
cd "$DEPLOY_PATH"
# 设置虚拟环境
setup_venv "venv"
# 安装依赖
install_dependencies
# 运行迁移
run_migrations
# 创建服务文件(非开发环境)
if [ "$environment" != "development" ] && [ -n "$SERVICE_NAME" ]; then
create_systemd_service "$SERVICE_NAME" "$DEPLOY_PATH"
create_nginx_config "$domain" "$app_port"
fi
log_info "部署完成!"
if [ "$environment" != "development" ]; then
log_info "启动服务..."
sudo systemctl start "$SERVICE_NAME"
sudo systemctl status "$SERVICE_NAME"
else
log_info "开发环境部署完成,使用以下命令启动:"
log_info "cd $DEPLOY_PATH && source venv/bin/activate && python start_dashboard.py"
fi
}
# 回滚函数
rollback() {
local backup_name=$1
if [ -z "$backup_name" ]; then
log_error "请指定备份名称"
exit 1
fi
log_info "回滚到备份: $backup_name"
# 停止服务
sudo systemctl stop tsp_assistant
# 恢复备份
if [ -d "backups/$backup_name" ]; then
sudo rm -rf /opt/tsp_assistant
sudo cp -r "backups/$backup_name" /opt/tsp_assistant
sudo chown -R www-data:www-data /opt/tsp_assistant
# 重启服务
sudo systemctl start tsp_assistant
log_info "回滚完成"
else
log_error "备份不存在: $backup_name"
exit 1
fi
}
# 版本检查
check_version() {
log_info "检查版本信息..."
if [ -f "version.json" ]; then
local version=$(python3 -c "import json; print(json.load(open('version.json'))['version'])" 2>/dev/null || echo "unknown")
log_info "当前版本: $version"
else
log_warn "版本文件不存在"
fi
}
# 创建部署包
create_deployment_package() {
local package_name="tsp_assistant_$(date +%Y%m%d_%H%M%S).tar.gz"
log_info "创建部署包: $package_name"
# 排除不需要的文件
tar --exclude='.git' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.env' \
--exclude='logs/*' \
--exclude='backups/*' \
--exclude='dev_deploy' \
-czf "$package_name" .
log_info "部署包创建完成: $package_name"
echo "$package_name"
}
# 主函数
main() {
case ${1:-deploy} in
deploy)
check_version
deploy "$2" "$3" "$4"
;;
rollback)
rollback "$2"
;;
package)
create_deployment_package
;;
*)
echo "用法: $0 {deploy|rollback|package} [environment] [domain] [port]"
echo "环境: development, staging, production"
echo ""
echo "命令说明:"
echo " deploy - 部署到指定环境"
echo " rollback - 回滚到指定备份"
echo " package - 创建部署包"
exit 1
;;
esac
}
main "$@"

204
scripts/docker_deploy.sh Normal file
View File

@@ -0,0 +1,204 @@
#!/bin/bash
# TSP智能助手Docker部署脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查Docker和Docker Compose
check_dependencies() {
log_info "检查依赖..."
if ! command -v docker &> /dev/null; then
log_error "Docker未安装请先安装Docker"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
log_error "Docker Compose未安装请先安装Docker Compose"
exit 1
fi
log_success "依赖检查通过"
}
# 创建必要的目录
create_directories() {
log_info "创建必要的目录..."
mkdir -p logs/nginx
mkdir -p monitoring/grafana/provisioning/datasources
mkdir -p monitoring/grafana/provisioning/dashboards
mkdir -p ssl
mkdir -p data
mkdir -p backups
mkdir -p uploads
mkdir -p config
log_success "目录创建完成"
}
# 构建镜像
build_images() {
log_info "构建Docker镜像..."
# 构建主应用镜像
docker-compose build --no-cache tsp-assistant
log_success "镜像构建完成"
}
# 启动服务
start_services() {
log_info "启动服务..."
# 启动基础服务MySQL, Redis
docker-compose up -d mysql redis
# 等待数据库启动
log_info "等待数据库启动..."
sleep 30
# 启动主应用
docker-compose up -d tsp-assistant
# 启动其他服务
docker-compose up -d nginx prometheus grafana
log_success "服务启动完成"
}
# 检查服务状态
check_services() {
log_info "检查服务状态..."
sleep 10
# 检查主应用
if curl -f http://localhost:5000/api/health &> /dev/null; then
log_success "TSP助手服务正常"
else
log_warning "TSP助手服务可能未完全启动"
fi
# 检查Nginx
if curl -f http://localhost/health &> /dev/null; then
log_success "Nginx服务正常"
else
log_warning "Nginx服务可能未完全启动"
fi
# 检查Prometheus
if curl -f http://localhost:9090 &> /dev/null; then
log_success "Prometheus服务正常"
else
log_warning "Prometheus服务可能未完全启动"
fi
# 检查Grafana
if curl -f http://localhost:3000 &> /dev/null; then
log_success "Grafana服务正常"
else
log_warning "Grafana服务可能未完全启动"
fi
}
# 显示服务信息
show_info() {
log_info "服务访问信息:"
echo " TSP助手: http://localhost:5000"
echo " Nginx代理: http://localhost"
echo " Prometheus: http://localhost:9090"
echo " Grafana: http://localhost:3000 (admin/admin123456)"
echo " MySQL: localhost:3306 (root/root123456)"
echo " Redis: localhost:6379 (密码: redis123456)"
echo ""
log_info "查看日志命令:"
echo " docker-compose logs -f tsp-assistant"
echo " docker-compose logs -f mysql"
echo " docker-compose logs -f redis"
echo " docker-compose logs -f nginx"
}
# 停止服务
stop_services() {
log_info "停止服务..."
docker-compose down
log_success "服务已停止"
}
# 清理资源
cleanup() {
log_info "清理Docker资源..."
docker system prune -f
log_success "清理完成"
}
# 主函数
main() {
case "${1:-start}" in
"start")
check_dependencies
create_directories
build_images
start_services
check_services
show_info
;;
"stop")
stop_services
;;
"restart")
stop_services
sleep 5
start_services
check_services
show_info
;;
"cleanup")
stop_services
cleanup
;;
"logs")
docker-compose logs -f "${2:-tsp-assistant}"
;;
"status")
docker-compose ps
;;
*)
echo "用法: $0 {start|stop|restart|cleanup|logs|status}"
echo " start - 启动所有服务"
echo " stop - 停止所有服务"
echo " restart - 重启所有服务"
echo " cleanup - 清理Docker资源"
echo " logs - 查看日志 (可选指定服务名)"
echo " status - 查看服务状态"
exit 1
;;
esac
}
# 执行主函数
main "$@"

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
批量为已有知识库条目生成 Embedding 向量(本地模型)
运行方式: python scripts/migrate_embeddings.py
首次运行会自动下载模型(~95MB之后走本地缓存
"""
import sys
import os
import json
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.config.unified_config import get_config
from src.core.database import db_manager
from src.core.models import KnowledgeEntry
from src.core.embedding_client import EmbeddingClient
from src.core.vector_store import vector_store
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def migrate():
config = get_config()
if not config.embedding.enabled:
logger.warning("Embedding 功能未启用,请在 .env 中设置 EMBEDDING_ENABLED=True")
return
client = EmbeddingClient()
# 测试模型加载
logger.info("正在加载本地 embedding 模型(首次运行需下载)...")
if not client.test_connection():
logger.error("Embedding 模型加载失败,请检查 sentence-transformers 是否安装")
return
logger.info("模型加载成功")
# 获取所有需要生成 embedding 的条目
with db_manager.get_session() as session:
entries = session.query(KnowledgeEntry).filter(
KnowledgeEntry.is_active == True
).all()
# 筛选出没有 embedding 的条目
to_process = []
for entry in entries:
if not entry.vector_embedding or entry.vector_embedding.strip() == '':
to_process.append(entry)
logger.info(f"{len(entries)} 条活跃知识,{len(to_process)} 条需要生成 embedding")
if not to_process:
logger.info("所有条目已有 embedding无需迁移")
return
# 批量生成
texts = [e.question + " " + e.answer for e in to_process]
logger.info(f"开始批量生成 embedding...")
vectors = client.embed_batch(texts)
success_count = 0
for i, entry in enumerate(to_process):
vec = vectors[i]
if vec:
entry.vector_embedding = json.dumps(vec)
success_count += 1
session.commit()
logger.info(f"Embedding 生成完成: 成功 {success_count}/{len(to_process)}")
# 重建向量索引
vector_store.load_from_db()
logger.info(f"向量索引重建完成: {vector_store.size}")
if __name__ == "__main__":
migrate()

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
多租户迁移脚本
为现有数据表添加 tenant_id 字段,已有数据填充为 'default'
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.core.database import db_manager
from sqlalchemy import text, inspect
TABLES_TO_MIGRATE = [
"work_orders",
"chat_sessions",
"conversations",
"knowledge_entries",
"analytics",
"alerts",
"users",
]
def migrate():
print("=" * 50)
print("多租户迁移: 添加 tenant_id 字段")
print("=" * 50)
with db_manager.get_session() as session:
inspector = inspect(session.bind)
for table in TABLES_TO_MIGRATE:
# 检查表是否存在
if table not in inspector.get_table_names():
print(f" [跳过] 表 {table} 不存在")
continue
# 检查字段是否已存在
columns = [col["name"] for col in inspector.get_columns(table)]
if "tenant_id" in columns:
print(f" [已有] 表 {table} 已包含 tenant_id")
continue
# 添加字段
print(f" [迁移] 表 {table} 添加 tenant_id ...")
session.execute(text(
f"ALTER TABLE {table} ADD COLUMN tenant_id VARCHAR(50) DEFAULT 'default' NOT NULL"
))
session.commit()
print(f" [完成] 表 {table} 迁移成功")
print("\n迁移完成!")
if __name__ == "__main__":
migrate()

277
scripts/monitor.sh Normal file
View File

@@ -0,0 +1,277 @@
#!/bin/bash
# TSP智能助手监控脚本
# 配置变量
APP_NAME="tsp_assistant"
SERVICE_NAME="tsp_assistant"
HEALTH_URL="http://localhost:5000/api/health"
LOG_FILE="./logs/monitor.log"
ALERT_EMAIL="admin@example.com"
ALERT_PHONE="13800138000"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 日志函数
log_info() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] INFO${NC} $1" | tee -a "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARN${NC} $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR${NC} $1" | tee -a "$LOG_FILE"
}
# 发送告警
send_alert() {
local message=$1
local level=$2
log_error "告警: $message"
# 发送邮件告警
if command -v mail &> /dev/null; then
echo "$message" | mail -s "[$level] TSP助手告警" "$ALERT_EMAIL"
fi
# 发送短信告警(需要配置短信服务)
# curl -X POST "https://api.sms.com/send" \
# -d "phone=$ALERT_PHONE" \
# -d "message=$message"
}
# 检查服务状态
check_service_status() {
if systemctl is-active --quiet "$SERVICE_NAME"; then
return 0
else
return 1
fi
}
# 检查健康状态
check_health() {
local response_code
response_code=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null)
if [ "$response_code" = "200" ]; then
return 0
else
return 1
fi
}
# 检查响应时间
check_response_time() {
local response_time
response_time=$(curl -s -o /dev/null -w "%{time_total}" "$HEALTH_URL" 2>/dev/null)
# 响应时间超过5秒认为异常
if (( $(echo "$response_time > 5.0" | bc -l) )); then
return 1
else
return 0
fi
}
# 检查系统资源
check_system_resources() {
local cpu_usage
local memory_usage
local disk_usage
# CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | awk -F'%' '{print $1}')
# 内存使用率
memory_usage=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100.0}')
# 磁盘使用率
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
# 检查阈值
if (( $(echo "$cpu_usage > 80" | bc -l) )); then
send_alert "CPU使用率过高: ${cpu_usage}%" "HIGH"
fi
if (( $(echo "$memory_usage > 80" | bc -l) )); then
send_alert "内存使用率过高: ${memory_usage}%" "HIGH"
fi
if [ "$disk_usage" -gt 80 ]; then
send_alert "磁盘使用率过高: ${disk_usage}%" "HIGH"
fi
log_info "系统资源 - CPU: ${cpu_usage}%, 内存: ${memory_usage}%, 磁盘: ${disk_usage}%"
}
# 检查日志错误
check_log_errors() {
local log_file="./logs/tsp_assistant.log"
local error_count
if [ -f "$log_file" ]; then
# 检查最近5分钟的错误日志
error_count=$(tail -n 100 "$log_file" | grep -c "ERROR" 2>/dev/null || echo "0")
if [ "$error_count" -gt 10 ]; then
send_alert "最近5分钟错误日志过多: $error_count" "MEDIUM"
fi
fi
}
# 检查数据库连接
check_database() {
local db_file="./tsp_assistant.db"
if [ -f "$db_file" ]; then
# 检查数据库文件大小
local db_size
db_size=$(du -h "$db_file" | cut -f1)
log_info "数据库大小: $db_size"
# 检查数据库是否可读
if ! sqlite3 "$db_file" "SELECT 1;" > /dev/null 2>&1; then
send_alert "数据库连接失败" "CRITICAL"
return 1
fi
fi
return 0
}
# 自动重启服务
restart_service() {
log_warn "尝试重启服务..."
sudo systemctl restart "$SERVICE_NAME"
sleep 10
if check_service_status && check_health; then
log_info "服务重启成功"
return 0
else
log_error "服务重启失败"
return 1
fi
}
# 主监控循环
monitor_loop() {
local consecutive_failures=0
local max_failures=3
while true; do
log_info "开始监控检查..."
# 检查服务状态
if ! check_service_status; then
log_error "服务未运行"
send_alert "TSP助手服务未运行" "CRITICAL"
consecutive_failures=$((consecutive_failures + 1))
else
# 检查健康状态
if ! check_health; then
log_error "健康检查失败"
send_alert "TSP助手健康检查失败" "HIGH"
consecutive_failures=$((consecutive_failures + 1))
else
# 检查响应时间
if ! check_response_time; then
log_warn "响应时间过长"
send_alert "TSP助手响应时间过长" "MEDIUM"
fi
consecutive_failures=0
fi
fi
# 检查系统资源
check_system_resources
# 检查日志错误
check_log_errors
# 检查数据库
check_database
# 连续失败处理
if [ "$consecutive_failures" -ge "$max_failures" ]; then
log_error "连续失败次数达到阈值,尝试重启服务"
if restart_service; then
consecutive_failures=0
else
send_alert "TSP助手服务重启失败需要人工干预" "CRITICAL"
fi
fi
# 等待下次检查
sleep 60
done
}
# 一次性检查
single_check() {
log_info "执行一次性健康检查..."
if check_service_status; then
log_info "✓ 服务运行正常"
else
log_error "✗ 服务未运行"
exit 1
fi
if check_health; then
log_info "✓ 健康检查通过"
else
log_error "✗ 健康检查失败"
exit 1
fi
if check_response_time; then
log_info "✓ 响应时间正常"
else
log_warn "⚠ 响应时间过长"
fi
check_system_resources
check_log_errors
check_database
log_info "健康检查完成"
}
# 主函数
main() {
# 创建日志目录
mkdir -p logs
case ${1:-monitor} in
monitor)
log_info "启动TSP助手监控服务..."
monitor_loop
;;
check)
single_check
;;
restart)
restart_service
;;
*)
echo "用法: $0 {monitor|check|restart}"
echo " monitor - 持续监控模式"
echo " check - 一次性健康检查"
echo " restart - 重启服务"
exit 1
;;
esac
}
# 执行主函数
main "$@"

285
scripts/quick_update.bat Normal file
View File

@@ -0,0 +1,285 @@
@echo off
REM TSP智能助手快速更新脚本 (Windows)
REM 支持热更新和完整更新
setlocal enabledelayedexpansion
REM 颜色定义
set "GREEN=[32m"
set "YELLOW=[33m"
set "RED=[31m"
set "NC=[0m"
REM 配置变量
set "APP_NAME=tsp_assistant"
set "DEPLOY_PATH=."
set "BACKUP_PATH=.\backups"
set "HEALTH_URL=http://localhost:5000/api/health"
REM 解析参数
set "ACTION=%1"
set "SOURCE_PATH=%2"
set "ENVIRONMENT=%3"
if "%ACTION%"=="" (
echo 用法: %0 {check^|hot-update^|full-update^|auto-update^|rollback} [源路径] [环境]
echo.
echo 命令说明:
echo check - 检查更新可用性
echo hot-update - 热更新(不重启服务)
echo full-update - 完整更新(重启服务)
echo auto-update - 自动更新(智能选择)
echo rollback - 回滚到指定备份
echo.
echo 环境: development, staging, production
exit /b 1
)
if "%ENVIRONMENT%"=="" set "ENVIRONMENT=production"
if "%SOURCE_PATH%"=="" set "SOURCE_PATH=."
REM 日志函数
:log_info
echo %GREEN%[INFO]%NC% %~1
goto :eof
:log_warn
echo %YELLOW%[WARN]%NC% %~1
goto :eof
:log_error
echo %RED%[ERROR]%NC% %~1
goto :eof
REM 检查更新可用性
:check_update
call :log_info "检查更新可用性..."
if not exist "%SOURCE_PATH%\version.json" (
call :log_error "源路径中未找到版本文件"
exit /b 1
)
REM 比较版本
for /f "tokens=2 delims=:" %%a in ('findstr "version" "%SOURCE_PATH%\version.json"') do (
set "NEW_VERSION=%%a"
set "NEW_VERSION=!NEW_VERSION: =!"
set "NEW_VERSION=!NEW_VERSION:"=!"
set "NEW_VERSION=!NEW_VERSION:,=!"
)
if exist "version.json" (
for /f "tokens=2 delims=:" %%a in ('findstr "version" "version.json"') do (
set "CURRENT_VERSION=%%a"
set "CURRENT_VERSION=!CURRENT_VERSION: =!"
set "CURRENT_VERSION=!CURRENT_VERSION:"=!"
set "CURRENT_VERSION=!CURRENT_VERSION:,=!"
)
) else (
set "CURRENT_VERSION=unknown"
)
if "!NEW_VERSION!"=="!CURRENT_VERSION!" (
call :log_info "没有更新可用 (当前版本: !CURRENT_VERSION!)"
) else (
call :log_info "发现更新: !CURRENT_VERSION! -> !NEW_VERSION!"
)
goto :eof
REM 创建备份
:create_backup
set "TIMESTAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%"
set "TIMESTAMP=!TIMESTAMP: =0!"
set "BACKUP_NAME=%APP_NAME%_backup_!TIMESTAMP!"
call :log_info "创建备份: !BACKUP_NAME!"
if not exist "%BACKUP_PATH%" mkdir "%BACKUP_PATH%"
mkdir "%BACKUP_PATH%\!BACKUP_NAME!"
REM 备份应用文件
if exist "%DEPLOY_PATH%" (
call :log_info "备份应用文件..."
xcopy "%DEPLOY_PATH%\*" "%BACKUP_PATH%\!BACKUP_NAME!\" /E /I /Y
)
REM 备份数据库
if exist "%DEPLOY_PATH%\tsp_assistant.db" (
call :log_info "备份数据库..."
mkdir "%BACKUP_PATH%\!BACKUP_NAME!\database"
copy "%DEPLOY_PATH%\tsp_assistant.db" "%BACKUP_PATH%\!BACKUP_NAME!\database\"
)
call :log_info "备份完成: !BACKUP_NAME!"
echo !BACKUP_NAME!
goto :eof
REM 热更新
:hot_update
call :log_info "开始热更新..."
REM 支持热更新的文件列表
set "HOT_UPDATE_FILES=src\web\static\js\dashboard.js src\web\static\css\style.css src\web\templates\dashboard.html src\web\app.py"
set "UPDATED_COUNT=0"
for %%f in (%HOT_UPDATE_FILES%) do (
if exist "%SOURCE_PATH%\%%f" (
call :log_info "更新文件: %%f"
if not exist "%DEPLOY_PATH%\%%f" mkdir "%DEPLOY_PATH%\%%f" 2>nul
copy "%SOURCE_PATH%\%%f" "%DEPLOY_PATH%\%%f" /Y >nul
set /a UPDATED_COUNT+=1
)
)
if !UPDATED_COUNT! gtr 0 (
call :log_info "热更新完成,更新了 !UPDATED_COUNT! 个文件"
) else (
call :log_info "没有文件需要热更新"
)
goto :eof
REM 完整更新
:full_update
call :log_info "开始完整更新..."
REM 创建备份
call :create_backup
set "BACKUP_NAME=!BACKUP_NAME!"
REM 停止服务(如果运行中)
call :log_info "停止服务..."
taskkill /f /im python.exe 2>nul || echo 服务未运行
REM 更新文件
call :log_info "更新应用文件..."
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
mkdir "%DEPLOY_PATH%"
xcopy "%SOURCE_PATH%\*" "%DEPLOY_PATH%\" /E /I /Y
REM 安装依赖
call :log_info "安装依赖..."
cd "%DEPLOY_PATH%"
if exist "requirements.txt" (
pip install -r requirements.txt
)
REM 运行数据库迁移
call :log_info "运行数据库迁移..."
if exist "init_database.py" (
python init_database.py
)
REM 启动服务
call :log_info "启动服务..."
start /b python start_dashboard.py
REM 等待服务启动
call :log_info "等待服务启动..."
timeout /t 15 /nobreak >nul
REM 健康检查
call :log_info "执行健康检查..."
set "RETRY_COUNT=0"
set "MAX_RETRIES=10"
:health_check_loop
if !RETRY_COUNT! geq !MAX_RETRIES! (
call :log_error "健康检查失败,开始回滚..."
call :rollback !BACKUP_NAME!
exit /b 1
)
curl -f "%HEALTH_URL%" >nul 2>&1
if !errorlevel! equ 0 (
call :log_info "健康检查通过!"
call :log_info "更新成功!"
call :log_info "备份名称: !BACKUP_NAME!"
exit /b 0
) else (
call :log_warn "健康检查失败,重试中... (!RETRY_COUNT!/!MAX_RETRIES!)"
set /a RETRY_COUNT+=1
timeout /t 5 /nobreak >nul
goto :health_check_loop
)
REM 回滚
:rollback
set "BACKUP_NAME=%1"
if "%BACKUP_NAME%"=="" (
call :log_error "请指定备份名称"
exit /b 1
)
call :log_info "开始回滚到备份: !BACKUP_NAME!"
if not exist "%BACKUP_PATH%\!BACKUP_NAME!" (
call :log_error "备份不存在: !BACKUP_NAME!"
exit /b 1
)
REM 停止服务
call :log_info "停止服务..."
taskkill /f /im python.exe 2>nul || echo 服务未运行
REM 恢复文件
call :log_info "恢复文件..."
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
mkdir "%DEPLOY_PATH%"
xcopy "%BACKUP_PATH%\!BACKUP_NAME!\*" "%DEPLOY_PATH%\" /E /I /Y
REM 恢复数据库
if exist "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" (
call :log_info "恢复数据库..."
copy "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" "%DEPLOY_PATH%\"
)
REM 启动服务
call :log_info "启动服务..."
cd "%DEPLOY_PATH%"
start /b python start_dashboard.py
REM 等待服务启动
timeout /t 15 /nobreak >nul
REM 健康检查
curl -f "%HEALTH_URL%" >nul 2>&1
if !errorlevel! equ 0 (
call :log_info "回滚成功!"
) else (
call :log_error "回滚后健康检查失败"
exit /b 1
)
goto :eof
REM 自动更新
:auto_update
call :log_info "开始自动更新..."
REM 尝试热更新
call :hot_update
if !errorlevel! equ 0 (
call :log_info "热更新成功"
exit /b 0
)
REM 热更新失败,进行完整更新
call :log_info "热更新失败,进行完整更新..."
call :full_update
goto :eof
REM 主逻辑
if "%ACTION%"=="check" (
call :check_update
) else if "%ACTION%"=="hot-update" (
call :hot_update
) else if "%ACTION%"=="full-update" (
call :full_update
) else if "%ACTION%"=="auto-update" (
call :auto_update
) else if "%ACTION%"=="rollback" (
call :rollback "%SOURCE_PATH%"
) else (
call :log_error "未知操作: %ACTION%"
exit /b 1
)
endlocal

477
scripts/update_manager.py Normal file
View File

@@ -0,0 +1,477 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TSP智能助手更新管理器
支持热更新、版本管理、回滚等功能
"""
import os
import sys
import json
import shutil
import subprocess
import time
import requests
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
class UpdateManager:
"""更新管理器"""
def __init__(self, config_file: str = "update_config.json"):
self.config_file = config_file
self.config = self._load_config()
self.version_manager = None
# 初始化版本管理器
try:
from version import VersionManager
self.version_manager = VersionManager()
except ImportError:
print("警告: 版本管理器不可用")
def _load_config(self) -> Dict:
"""加载更新配置"""
default_config = {
"app_name": "tsp_assistant",
"deploy_path": "/opt/tsp_assistant",
"backup_path": "./backups",
"service_name": "tsp_assistant",
"health_url": "http://localhost:5000/api/health",
"update_timeout": 300,
"rollback_enabled": True,
"auto_backup": True,
"hot_update_enabled": True,
"environments": {
"development": {
"path": "./dev_deploy",
"service_name": "",
"auto_restart": False
},
"staging": {
"path": "/opt/tsp_assistant_staging",
"service_name": "tsp_assistant_staging",
"auto_restart": True
},
"production": {
"path": "/opt/tsp_assistant",
"service_name": "tsp_assistant",
"auto_restart": True
}
}
}
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
# 合并默认配置
default_config.update(config)
except Exception as e:
print(f"加载配置文件失败: {e}")
return default_config
def _save_config(self):
"""保存配置"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"保存配置文件失败: {e}")
def check_update_available(self, source_path: str) -> Tuple[bool, str, str]:
"""检查是否有更新可用"""
if not self.version_manager:
return False, "unknown", "unknown"
current_version = self.version_manager.get_version()
# 检查源路径的版本
try:
source_version_file = os.path.join(source_path, "version.json")
if os.path.exists(source_version_file):
with open(source_version_file, 'r', encoding='utf-8') as f:
source_info = json.load(f)
source_version = source_info.get("version", "unknown")
else:
return False, current_version, "unknown"
except Exception as e:
print(f"检查源版本失败: {e}")
return False, current_version, "unknown"
# 比较版本
if source_version != current_version:
return True, current_version, source_version
return False, current_version, source_version
def create_backup(self, environment: str = "production") -> str:
"""创建备份"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"{self.config['app_name']}_backup_{timestamp}"
backup_path = os.path.join(self.config["backup_path"], backup_name)
print(f"创建备份: {backup_name}")
# 创建备份目录
os.makedirs(backup_path, exist_ok=True)
# 获取部署路径
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
# 备份应用文件
if os.path.exists(deploy_path):
print("备份应用文件...")
shutil.copytree(deploy_path, os.path.join(backup_path, "app"))
# 备份数据库
db_file = os.path.join(deploy_path, "tsp_assistant.db")
if os.path.exists(db_file):
print("备份数据库...")
os.makedirs(os.path.join(backup_path, "database"), exist_ok=True)
shutil.copy2(db_file, os.path.join(backup_path, "database", "tsp_assistant.db"))
# 保存备份信息
backup_info = {
"backup_name": backup_name,
"backup_path": backup_path,
"timestamp": timestamp,
"environment": environment,
"version": self.version_manager.get_version() if self.version_manager else "unknown",
"git_commit": self._get_git_commit(deploy_path)
}
with open(os.path.join(backup_path, "backup_info.json"), 'w', encoding='utf-8') as f:
json.dump(backup_info, f, indent=2, ensure_ascii=False)
print(f"备份完成: {backup_name}")
return backup_name
def _get_git_commit(self, path: str) -> str:
"""获取Git提交哈希"""
try:
result = subprocess.run(['git', 'rev-parse', 'HEAD'],
cwd=path, capture_output=True, text=True)
return result.stdout.strip()[:8] if result.returncode == 0 else "unknown"
except:
return "unknown"
def hot_update(self, source_path: str, environment: str = "production") -> bool:
"""热更新(不重启服务)"""
if not self.config["hot_update_enabled"]:
print("热更新未启用")
return False
print("开始热更新...")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
# 检查哪些文件可以热更新
hot_update_files = [
"src/web/static/js/dashboard.js",
"src/web/static/css/style.css",
"src/web/templates/dashboard.html",
"src/web/app.py",
"src/knowledge_base/knowledge_manager.py",
"src/dialogue/realtime_chat.py"
]
updated_files = []
for file_path in hot_update_files:
source_file = os.path.join(source_path, file_path)
target_file = os.path.join(deploy_path, file_path)
if os.path.exists(source_file):
# 检查文件是否有变化
if not os.path.exists(target_file) or not self._files_equal(source_file, target_file):
print(f"更新文件: {file_path}")
os.makedirs(os.path.dirname(target_file), exist_ok=True)
shutil.copy2(source_file, target_file)
updated_files.append(file_path)
if updated_files:
print(f"热更新完成,更新了 {len(updated_files)} 个文件")
return True
else:
print("没有文件需要热更新")
return False
def _files_equal(self, file1: str, file2: str) -> bool:
"""比较两个文件是否相等"""
try:
with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
return f1.read() == f2.read()
except:
return False
def full_update(self, source_path: str, environment: str = "production",
create_backup: bool = True) -> bool:
"""完整更新(重启服务)"""
print("开始完整更新...")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
service_name = env_config.get("service_name", self.config["service_name"])
auto_restart = env_config.get("auto_restart", True)
# 创建备份
backup_name = None
if create_backup and self.config["auto_backup"]:
backup_name = self.create_backup(environment)
try:
# 停止服务
if auto_restart and service_name:
print(f"停止服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
# 更新文件
print("更新应用文件...")
if os.path.exists(deploy_path):
shutil.rmtree(deploy_path)
os.makedirs(deploy_path, exist_ok=True)
shutil.copytree(source_path, deploy_path, dirs_exist_ok=True)
# 设置权限
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
# 安装依赖
print("安装依赖...")
requirements_file = os.path.join(deploy_path, "requirements.txt")
if os.path.exists(requirements_file):
subprocess.run(['sudo', '-u', 'www-data', 'python', '-m', 'pip', 'install', '-r', requirements_file],
cwd=deploy_path, check=True)
# 运行数据库迁移
print("运行数据库迁移...")
init_script = os.path.join(deploy_path, "init_database.py")
if os.path.exists(init_script):
subprocess.run(['sudo', '-u', 'www-data', 'python', init_script],
cwd=deploy_path, check=True)
# 启动服务
if auto_restart and service_name:
print(f"启动服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
# 等待服务启动
print("等待服务启动...")
time.sleep(15)
# 健康检查
if self._health_check():
print("更新成功!")
return True
else:
print("健康检查失败,开始回滚...")
if backup_name:
self.rollback(backup_name, environment)
return False
else:
print("更新完成(未重启服务)")
return True
except Exception as e:
print(f"更新失败: {e}")
if backup_name:
print("开始回滚...")
self.rollback(backup_name, environment)
return False
def _health_check(self) -> bool:
"""健康检查"""
health_url = self.config["health_url"]
max_retries = 10
retry_count = 0
while retry_count < max_retries:
try:
response = requests.get(health_url, timeout=5)
if response.status_code == 200:
return True
except:
pass
retry_count += 1
print(f"健康检查失败,重试中... ({retry_count}/{max_retries})")
time.sleep(5)
return False
def rollback(self, backup_name: str, environment: str = "production") -> bool:
"""回滚到指定备份"""
print(f"开始回滚到备份: {backup_name}")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
service_name = env_config.get("service_name", self.config["service_name"])
auto_restart = env_config.get("auto_restart", True)
backup_path = os.path.join(self.config["backup_path"], backup_name)
if not os.path.exists(backup_path):
print(f"备份不存在: {backup_name}")
return False
try:
# 停止服务
if auto_restart and service_name:
print(f"停止服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
# 恢复文件
print("恢复文件...")
app_backup_path = os.path.join(backup_path, "app")
if os.path.exists(app_backup_path):
if os.path.exists(deploy_path):
shutil.rmtree(deploy_path)
shutil.copytree(app_backup_path, deploy_path)
# 恢复数据库
db_backup_path = os.path.join(backup_path, "database", "tsp_assistant.db")
if os.path.exists(db_backup_path):
print("恢复数据库...")
shutil.copy2(db_backup_path, os.path.join(deploy_path, "tsp_assistant.db"))
# 设置权限
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
# 启动服务
if auto_restart and service_name:
print(f"启动服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
# 等待服务启动
time.sleep(15)
# 健康检查
if self._health_check():
print("回滚成功!")
return True
else:
print("回滚后健康检查失败")
return False
else:
print("回滚完成(未重启服务)")
return True
except Exception as e:
print(f"回滚失败: {e}")
return False
def list_backups(self) -> List[Dict]:
"""列出所有备份"""
backups = []
backup_dir = self.config["backup_path"]
if os.path.exists(backup_dir):
for item in os.listdir(backup_dir):
backup_path = os.path.join(backup_dir, item)
if os.path.isdir(backup_path):
info_file = os.path.join(backup_path, "backup_info.json")
if os.path.exists(info_file):
try:
with open(info_file, 'r', encoding='utf-8') as f:
backup_info = json.load(f)
backups.append(backup_info)
except:
pass
return sorted(backups, key=lambda x: x.get("timestamp", ""), reverse=True)
def auto_update(self, source_path: str, environment: str = "production") -> bool:
"""自动更新(智能选择热更新或完整更新)"""
print("开始自动更新...")
# 检查是否有更新
has_update, current_version, new_version = self.check_update_available(source_path)
if not has_update:
print("没有更新可用")
return True
print(f"发现更新: {current_version} -> {new_version}")
# 尝试热更新
if self.hot_update(source_path, environment):
print("热更新成功")
return True
# 热更新失败,进行完整更新
print("热更新失败,进行完整更新...")
return self.full_update(source_path, environment)
def main():
"""命令行接口"""
import argparse
parser = argparse.ArgumentParser(description='TSP智能助手更新管理器')
parser.add_argument('action', choices=['check', 'hot-update', 'full-update', 'auto-update', 'rollback', 'list-backups'],
help='要执行的操作')
parser.add_argument('--source', help='源路径')
parser.add_argument('--environment', choices=['development', 'staging', 'production'],
default='production', help='目标环境')
parser.add_argument('--backup', help='备份名称(用于回滚)')
parser.add_argument('--no-backup', action='store_true', help='跳过备份')
args = parser.parse_args()
um = UpdateManager()
if args.action == 'check':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
has_update, current, new = um.check_update_available(args.source)
if has_update:
print(f"有更新可用: {current} -> {new}")
else:
print(f"没有更新可用 (当前版本: {current})")
elif args.action == 'hot-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.hot_update(args.source, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'full-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.full_update(args.source, args.environment, not args.no_backup)
sys.exit(0 if success else 1)
elif args.action == 'auto-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.auto_update(args.source, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'rollback':
if not args.backup:
print("错误: 需要指定备份名称")
sys.exit(1)
success = um.rollback(args.backup, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'list-backups':
backups = um.list_backups()
if backups:
print("可用备份:")
for backup in backups:
print(f" {backup['backup_name']} - {backup['timestamp']} - {backup.get('version', 'unknown')}")
else:
print("没有找到备份")
if __name__ == "__main__":
main()

273
scripts/upgrade.sh Normal file
View File

@@ -0,0 +1,273 @@
#!/bin/bash
# TSP智能助手升级脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# 配置变量
APP_NAME="tsp_assistant"
BACKUP_DIR="./backups"
DEPLOY_PATH="/opt/tsp_assistant"
SERVICE_NAME="tsp_assistant"
HEALTH_URL="http://localhost:5000/api/health"
# 检查参数
if [ $# -lt 1 ]; then
echo "用法: $0 <新版本路径> [选项]"
echo "选项:"
echo " --force 强制升级,跳过确认"
echo " --no-backup 跳过备份"
echo " --rollback 回滚到指定备份"
exit 1
fi
NEW_VERSION_PATH=$1
FORCE_UPGRADE=false
SKIP_BACKUP=false
ROLLBACK_MODE=false
# 解析参数
while [[ $# -gt 1 ]]; do
case $2 in
--force)
FORCE_UPGRADE=true
;;
--no-backup)
SKIP_BACKUP=true
;;
--rollback)
ROLLBACK_MODE=true
;;
*)
log_error "未知选项: $2"
exit 1
;;
esac
shift
done
# 回滚功能
rollback() {
local backup_name=$1
if [ -z "$backup_name" ]; then
log_error "请指定备份名称"
exit 1
fi
log_step "开始回滚到备份: $backup_name"
# 检查备份是否存在
if [ ! -d "$BACKUP_DIR/$backup_name" ]; then
log_error "备份不存在: $backup_name"
log_info "可用备份列表:"
ls -la "$BACKUP_DIR" | grep backup
exit 1
fi
# 停止服务
log_info "停止服务..."
sudo systemctl stop "$SERVICE_NAME" || true
# 恢复文件
log_info "恢复文件..."
sudo rm -rf "$DEPLOY_PATH"
sudo cp -r "$BACKUP_DIR/$backup_name" "$DEPLOY_PATH"
sudo chown -R www-data:www-data "$DEPLOY_PATH"
# 恢复数据库
if [ -f "$BACKUP_DIR/$backup_name/database/tsp_assistant.db" ]; then
log_info "恢复数据库..."
sudo cp "$BACKUP_DIR/$backup_name/database/tsp_assistant.db" "$DEPLOY_PATH/"
fi
# 启动服务
log_info "启动服务..."
sudo systemctl start "$SERVICE_NAME"
# 等待服务启动
sleep 10
# 健康检查
if curl -f "$HEALTH_URL" > /dev/null 2>&1; then
log_info "回滚成功!"
else
log_error "回滚后健康检查失败"
exit 1
fi
}
# 创建备份
create_backup() {
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_name="${APP_NAME}_backup_${timestamp}"
local backup_path="$BACKUP_DIR/$backup_name"
log_step "创建备份: $backup_name"
# 创建备份目录
mkdir -p "$backup_path"
# 备份应用文件
if [ -d "$DEPLOY_PATH" ]; then
log_info "备份应用文件..."
cp -r "$DEPLOY_PATH"/* "$backup_path/"
fi
# 备份数据库
if [ -f "$DEPLOY_PATH/tsp_assistant.db" ]; then
log_info "备份数据库..."
mkdir -p "$backup_path/database"
cp "$DEPLOY_PATH/tsp_assistant.db" "$backup_path/database/"
fi
# 保存备份信息
cat > "$backup_path/backup_info.json" << EOF
{
"backup_name": "$backup_name",
"backup_path": "$backup_path",
"timestamp": "$timestamp",
"version": "$(cd "$DEPLOY_PATH" && python version.py version 2>/dev/null || echo "unknown")",
"git_commit": "$(cd "$DEPLOY_PATH" && git rev-parse HEAD 2>/dev/null | cut -c1-8 || echo "unknown")"
}
EOF
log_info "备份完成: $backup_name"
echo "$backup_name"
}
# 升级功能
upgrade() {
local new_version_path=$1
log_step "开始升级TSP智能助手"
# 检查新版本路径
if [ ! -d "$new_version_path" ]; then
log_error "新版本路径不存在: $new_version_path"
exit 1
fi
# 检查当前版本
if [ -d "$DEPLOY_PATH" ]; then
local current_version=$(cd "$DEPLOY_PATH" && python version.py version 2>/dev/null || echo "unknown")
log_info "当前版本: $current_version"
else
log_warn "当前部署路径不存在: $DEPLOY_PATH"
fi
# 检查新版本
local new_version=$(cd "$new_version_path" && python version.py version 2>/dev/null || echo "unknown")
log_info "新版本: $new_version"
# 确认升级
if [ "$FORCE_UPGRADE" = false ]; then
echo -n "确认升级到版本 $new_version? (y/N): "
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
log_info "升级取消"
exit 0
fi
fi
# 创建备份
local backup_name=""
if [ "$SKIP_BACKUP" = false ]; then
backup_name=$(create_backup)
fi
# 停止服务
log_step "停止服务..."
sudo systemctl stop "$SERVICE_NAME" || true
# 升级文件
log_step "升级应用文件..."
sudo rm -rf "$DEPLOY_PATH"
sudo mkdir -p "$DEPLOY_PATH"
sudo cp -r "$new_version_path"/* "$DEPLOY_PATH/"
sudo chown -R www-data:www-data "$DEPLOY_PATH"
# 安装依赖
log_step "安装依赖..."
cd "$DEPLOY_PATH"
sudo -u www-data python -m pip install -r requirements.txt
# 运行数据库迁移
log_step "运行数据库迁移..."
sudo -u www-data python init_database.py || true
# 启动服务
log_step "启动服务..."
sudo systemctl start "$SERVICE_NAME"
# 等待服务启动
log_info "等待服务启动..."
sleep 15
# 健康检查
log_step "执行健康检查..."
local retry_count=0
local max_retries=10
while [ $retry_count -lt $max_retries ]; do
if curl -f "$HEALTH_URL" > /dev/null 2>&1; then
log_info "健康检查通过!"
break
else
log_warn "健康检查失败,重试中... ($((retry_count + 1))/$max_retries)"
retry_count=$((retry_count + 1))
sleep 5
fi
done
if [ $retry_count -eq $max_retries ]; then
log_error "健康检查失败,开始回滚..."
if [ -n "$backup_name" ]; then
rollback "$backup_name"
else
log_error "没有备份可回滚"
exit 1
fi
else
log_info "升级成功!"
log_info "新版本: $new_version"
if [ -n "$backup_name" ]; then
log_info "备份名称: $backup_name"
fi
fi
}
# 主函数
main() {
if [ "$ROLLBACK_MODE" = true ]; then
rollback "$NEW_VERSION_PATH"
else
upgrade "$NEW_VERSION_PATH"
fi
}
# 执行主函数
main

3
src/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# TSP助手 - 基于大模型的AI客服机器人
__version__ = "1.0.0"
__author__ = "TSP Assistant Team"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +1,22 @@
# -*- coding: utf-8 -*-
"""
Agent模块
Agent模块初始化文件
"""
from .react_agent import ReactAgent
from .agent_core import AgentCore, AgentState
from .planner import TaskPlanner
from .executor import TaskExecutor
from .tool_manager import ToolManager
from .reasoning_engine import ReasoningEngine
from .goal_manager import GoalManager
__all__ = ['ReactAgent']
__all__ = [
'AgentCore',
'AgentState',
'TaskPlanner',
'TaskExecutor',
'ToolManager',
'ReasoningEngine',
'GoalManager'
]

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More