# php_knowledge **Repository Path**: web/php_knowledge ## Basic Information - **Project Name**: php_knowledge - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-06 - **Last Updated**: 2026-08-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 企业 AI 知识库客服 一个基于 PHP 原生实现的企业知识库客服 Demo。前端提供聊天界面,后端调用 OpenAI-compatible Responses API,并通过 Qdrant 向量检索企业知识片段,支持会话历史记录。 ## 功能 - 流式输出 AI 回复 - 基于 Qdrant 的知识库向量检索 - MySQL 保存会话和消息历史 - 支持 OpenAI-compatible 聊天模型和 Embedding 模型 - 前端通过统一 `/api/v1/*` API 请求后端 - 提供知识库管理页、同步脚本和 Qdrant 集合初始化脚本 - 支持知识文档聚合、版本替换、历史版本查看和回滚 ## 运行环境 - PHP 7.4+ - PHP 扩展:`curl`、`pdo_mysql`、`mbstring` - MySQL 5.6+ - Qdrant - OpenAI-compatible API Key ## 配置 复制环境变量模板并填写真实配置: ```bash cp .env.example .env ``` 主要配置项: ```ini OPENAI_BASE_URL=https://api.openai.com OPENAI_API_KEY=your-openai-api-key OPENAI_MODEL=your-chat-model OPENAI_MAX_OUTPUT_TOKENS=4096 OPENAI_STREAM_TIMEOUT_SECONDS=0 DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=ai_business_demo DB_USERNAME=your-db-user DB_PASSWORD=your-db-password QDRANT_URL=http://127.0.0.1:6333 QDRANT_COLLECTION=knowledge_chunks QDRANT_SCORE_THRESHOLD=0.45 QDRANT_STRICT_TERM_SCORE_THRESHOLD=0.30 QDRANT_VECTOR_SIZE=4096 QDRANT_UPSERT_BATCH_SIZE=64 EMBEDDING_BASE_URL=https://api.openai.com EMBEDDING_API_KEY=your-embedding-api-key EMBEDDING_MODEL=your-embedding-model EMBEDDING_TIMEOUT_SECONDS=8 EMBEDDING_MAX_RETRIES=5 EMBEDDING_RETRY_BASE_SECONDS=2 EMBEDDING_SYNC_SLEEP_MS=0 KNOWLEDGE_SEARCH_LIMIT=3 KNOWLEDGE_SEARCH_CANDIDATE_LIMIT=8 KNOWLEDGE_CHUNK_MAX_LENGTH=800 KNOWLEDGE_CHUNK_OVERLAP=100 KNOWLEDGE_UPLOAD_MAX_BYTES=2097152 CHAT_HISTORY_LIMIT=50 ``` 如果 `OPENAI_BASE_URL` 或 `EMBEDDING_BASE_URL` 已经包含 `/v1`,代码会自动复用该路径;否则会自动拼接 `/v1`。 可按需要调整: - `OPENAI_INSTRUCTIONS`:客服助手系统提示词 - `OPENAI_MAX_OUTPUT_TOKENS`:单次回复最大输出 token - `OPENAI_STREAM_TIMEOUT_SECONDS`:流式回复读取超时,`0` 表示不限制 - `KNOWLEDGE_SEARCH_LIMIT`:每次问答最终使用的知识片段数量 - `KNOWLEDGE_SEARCH_CANDIDATE_LIMIT`:向量召回候选数量,用于再筛选后取最终片段 - `KNOWLEDGE_CHUNK_MAX_LENGTH` / `KNOWLEDGE_CHUNK_OVERLAP`:后台录入知识时的切片大小和重叠长度 - `KNOWLEDGE_UPLOAD_MAX_BYTES`:后台文件上传大小上限 - `QDRANT_VECTOR_SIZE` / `QDRANT_DISTANCE`:Qdrant 集合向量维度和距离算法 - `QDRANT_STRICT_TERM_SCORE_THRESHOLD`:命中关键术语时使用的最低相似度阈值 - `EMBEDDING_TIMEOUT_SECONDS` / `QDRANT_TIMEOUT_SECONDS`:问答链路的知识库检索超时;超时后会降级为无参考资料回答,不阻断聊天 - `EMBEDDING_MAX_RETRIES` / `EMBEDDING_RETRY_BASE_SECONDS`:Embedding 临时失败时的重试次数和线性退避基准秒数 - `EMBEDDING_SYNC_SLEEP_MS`:批量同步 Qdrant 时每次生成向量后的停顿毫秒数,用于降低限流风险 - `QDRANT_UPSERT_TIMEOUT_SECONDS`:后台录入或批量同步知识库时的向量写入超时 - `QDRANT_UPSERT_BATCH_SIZE`:命令行批量同步 Qdrant 时每批写入的 point 数量 - `CHAT_HISTORY_LIMIT` / `SESSION_LIST_LIMIT`:会话上下文和会话列表数量 ## 数据库表 创建业务数据库后,可执行 `database/schema.sql`。完整 SQL 如下: ```sql CREATE TABLE knowledge_chunks ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, document_id VARCHAR(64) NOT NULL DEFAULT '', title VARCHAR(255) NOT NULL, content TEXT NOT NULL, source VARCHAR(255) DEFAULT '', document_type VARCHAR(32) NOT NULL DEFAULT 'note', priority TINYINT UNSIGNED NOT NULL DEFAULT 50, version INT UNSIGNED NOT NULL DEFAULT 1, status VARCHAR(16) NOT NULL DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_knowledge_chunks_document_id (document_id), INDEX idx_knowledge_chunks_document_status (document_id, status), INDEX idx_knowledge_chunks_document_version (document_id, version) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE chat_logs ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, session_id VARCHAR(64) NOT NULL, user_message TEXT NOT NULL, assistant_answer MEDIUMTEXT NOT NULL, model VARCHAR(128) NOT NULL, request_time_ms INT UNSIGNED DEFAULT 0, sources_json TEXT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_chat_logs_session_id (session_id), INDEX idx_chat_logs_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` 已有旧表可执行以下 SQL 手动补充参考资料快照列;应用保存或读取历史消息时也会自动检测并补列: ```sql ALTER TABLE chat_logs ADD COLUMN sources_json TEXT NULL AFTER request_time_ms; ``` 已有旧知识表如果还没有文档 ID、资料类型和优先级字段,可执行以下 SQL,并为常见资料来源设置初始权重: ```sql ALTER TABLE knowledge_chunks ADD COLUMN document_id VARCHAR(64) NOT NULL DEFAULT '' AFTER id; CREATE INDEX idx_knowledge_chunks_document_id ON knowledge_chunks (document_id); UPDATE knowledge_chunks SET document_id = CONCAT('doc_', id) WHERE document_id = ''; ALTER TABLE knowledge_chunks ADD COLUMN document_type VARCHAR(32) NOT NULL DEFAULT 'note' AFTER source; ALTER TABLE knowledge_chunks ADD COLUMN priority TINYINT UNSIGNED NOT NULL DEFAULT 50 AFTER document_type; UPDATE knowledge_chunks SET document_type = 'policy' WHERE title LIKE '%退款%' OR source LIKE '%售后%'; UPDATE knowledge_chunks SET priority = 90 WHERE title LIKE '%退款%' OR source LIKE '%售后%'; UPDATE knowledge_chunks SET document_type = 'manual' WHERE source LIKE '%产品说明%'; UPDATE knowledge_chunks SET priority = 70 WHERE source LIKE '%产品说明%'; UPDATE knowledge_chunks SET document_type = 'test' WHERE source LIKE '%test%' OR title LIKE '%test%'; UPDATE knowledge_chunks SET priority = 30 WHERE source LIKE '%test%' OR title LIKE '%test%'; ``` 如果旧知识表已经有 `document_id`、`document_type`、`priority`,但还没有文档版本字段,可执行 `database/migrations/20260707_add_knowledge_document_versions.sql`: ```sql ALTER TABLE knowledge_chunks ADD COLUMN version INT UNSIGNED NOT NULL DEFAULT 1 AFTER priority; ALTER TABLE knowledge_chunks ADD COLUMN status VARCHAR(16) NOT NULL DEFAULT 'active' AFTER version; CREATE INDEX idx_knowledge_chunks_document_status ON knowledge_chunks (document_id, status); CREATE INDEX idx_knowledge_chunks_document_version ON knowledge_chunks (document_id, version); ``` `version` 用于记录同一 `document_id` 的历史版本;`status='active'` 的片段是当前检索和命令行同步到 Qdrant 的有效版本,替换或回滚文档时会把旧版本标记为 `archived`。 向 `knowledge_chunks` 写入企业资料后,可同步到 Qdrant。 也可以打开后台录入页直接写入 MySQL 并同步到 Qdrant: ```text http://127.0.0.1:8000/knowledge_admin.html ``` ## 初始化 Qdrant 启动 Qdrant 后,创建集合: ```bash php bin/qdrant_cli.php init ``` 默认 `QDRANT_VECTOR_SIZE=4096`。如果你的 Embedding 模型输出维度不是 4096,请先在 `.env` 中修改该值,再初始化集合。 同步 MySQL 中当前 `active` 的知识片段到 Qdrant: ```bash php bin/qdrant_cli.php sync ``` ## 本地启动 使用 PHP 内置服务器: ```bash php -S 127.0.0.1:8000 router.php ``` 然后打开: ```text http://127.0.0.1:8000 ``` ## 虚拟主机部署 推荐将 `public/` 目录设为文档根目录,源码文件位于 Web 根之外,天然安全。 ### 目录结构 ``` 服务器目录结构: ├── .env ← 配置文件(在 Web 根之外,安全) ├── src/ ← 源码(在 Web 根之外,安全) ├── bin/ ← CLI 脚本(在 Web 根之外,安全) ├── database/ ← SQL 文件(在 Web 根之外,安全) └── public/ ← 此目录作为文档根 ├── index.php ← 全功能路由器(API + 静态文件 + SPA) ├── .htaccess ← Apache rewrite(可选,提升性能) ├── chat.html ← 首页 ├── knowledge_admin.html ← 知识库管理页 ├── api.php ← API 处理逻辑 └── assets/ ← 静态资源 ├── css/ └── js/ ├── vendor/ ← 第三方库(marked / dompurify) ├── api.js ├── chat.js ├── chat-renderer.js └── knowledge-admin.js ``` 部署步骤: 1. 将项目上传到服务器,确保 `public/` 目录外为 `src/`、`.env` 等目录 2. 将 `public/` 目录设为文档根目录(Document Root) 3. 配置伪静态(Apache 自动生效,Nginx 需手动配置) 4. 浏览器访问 `http://your-domain.com/` 即可 ### 工作原理 `public/index.php` 作为 **全功能路由器**,处理所有请求: | 请求路径 | 处理方式 | |---------|---------| | `/` | 返回首页(chat.html) | | `/api/v1/*` | 路由到 `api.php` 处理 API | | `/assets/css/*.css` | 返回静态文件 | | `/assets/js/*.js` | 返回静态文件 | | `/knowledge_admin.html` | 返回管理页 | | 其他路径 | SPA fallback,返回首页 | ### 伪静态配置 #### Apache(自动生效) 项目自带 `public/.htaccess`,Apache 会自动读取并生效: ```apache RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^api(?:/(.*))?$ index.php [L,QSA] RewriteRule ^ index.php [L] ``` 说明: - 启用 `mod_rewrite` 时,`/api/*` 请求自动 rewrite 到 `index.php` - 未启用 `mod_rewrite` 时,404 请求 fallback 到 `index.php` - 阻止访问 `.env`、`.git` 等敏感文件 - 禁止目录列表 #### Nginx 伪静态 在 Nginx 站点配置中添加以下内容: ```nginx location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=/$1 last; break; } } 或 location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last; break; } } ``` 说明: - `rewrite ^(.*)$ /index.php?s=/$1 last`:将非静态文件请求转发到 `index.php`,原始路径通过 `s` 参数传递 - `public/index.php` 已内置 `s` 参数识别,无需额外修改。 #### Apache 无 .htaccess 场景 极少数虚拟主机不允许使用 `.htaccess`,此时: - Apache 直接返回静态文件(`/assets/*`、`/chat.html` 等) - `/api/*` 请求返回 404,需要前端改用 `/api.php/api/*` 路径 在 `public/assets/js/api.js` 中批量替换: ```js // 将所有 '/api/v1/' 改为 '/api.php/api/v1/' // 例如: fetch('/api.php/api/v1/chat/stream', ...) ``` ## API 路由 前端统一通过 API 路由访问后端,不直接请求具体 PHP 脚本: - `POST /api/v1/chat/stream`:流式问答 - `POST /api/v1/chat`:非流式问答 - `GET /api/v1/sessions`:会话列表 - `GET /api/v1/sessions/{session_id}/messages`:会话消息 - `POST /api/v1/knowledge-chunks`:保存知识并同步到 Qdrant - `GET /api/v1/knowledge-chunks`:最近知识片段列表 - `GET /api/v1/knowledge-chunks/{id}`:获取单条知识片段 - `PUT /api/v1/knowledge-chunks/{id}`:更新单条知识片段并同步 Qdrant - `DELETE /api/v1/knowledge-chunks/{id}`:删除单条知识片段并删除 Qdrant point - `POST /api/v1/knowledge-chunks/sync`:将 MySQL 知识片段重新同步到 Qdrant - `POST /api/v1/knowledge-files`:上传 `txt` 或 `md` 知识文件 - `GET /api/v1/knowledge-documents`:按 `document_id` 聚合的文档列表,返回当前版本、active 片段数和总片段数 - `DELETE /api/v1/knowledge-documents/{document_id}`:删除整篇文档及其所有版本片段 - `POST /api/v1/knowledge-documents/{document_id}/replace`:把当前 active 版本归档,并保存新版本片段 - `GET /api/v1/knowledge-documents/{document_id}/versions`:查看文档历史版本 - `POST /api/v1/knowledge-documents/{document_id}/versions/{version}/rollback`:把指定历史版本恢复为 active,并重建对应 Qdrant points ## 文件说明 - `router.php`:PHP 内置服务器路由脚本 - `database/schema.sql`:新数据库初始化 SQL - `database/migrations/20260707_add_knowledge_document_versions.sql`:旧知识表补充文档版本字段的迁移 SQL - `public/`:前端页面和静态资源 - `public/index.php`:全功能路由器(虚拟主机入口) - `public/chat.html`:聊天界面 - `public/knowledge_admin.html`:知识库录入和同步页面 - `public/api.php`:API 处理入口 - `public/.htaccess`:Apache 伪静态配置 - `public/assets/js/vendor/`:第三方库(marked / dompurify) - `public/assets/js/api.js`:前端 API 客户端 - `public/assets/js/chat-renderer.js`:聊天消息和参考资料渲染 - `public/assets/js/chat.js`:聊天页交互入口 - `public/assets/js/knowledge-admin.js`:知识库管理页交互入口 - `src/`:后端 API 和公共方法 - `src/api.php`:统一 API 路由入口 - `src/api_handlers.php`:API handler 实现 - `src/app_helper.php`:环境读取、JSON 响应、PDO、OpenAI 请求等公共方法 - `src/knowledge_helper.php`:知识切片、Embedding、Qdrant、知识上下文构造等公共方法 - `bin/qdrant_cli.php`:Qdrant 集合初始化和知识库同步脚本 ## 安全说明 `.env`、日志、依赖目录、本地缓存和数据库文件已通过 `.gitignore` 排除。不要将真实 API Key、数据库密码或生产数据提交到 Git。