# genius-qa **Repository Path**: hulutech/genius-qa ## Basic Information - **Project Name**: genius-qa - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-26 - **Last Updated**: 2026-06-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 📚 Genius QA - 智能问答系统 基于 **Goravel v1.17** + **PostgreSQL(pgvector)** + **Ollama** 构建的政府智能问答系统,支持向量语义检索、全文检索混合查询,以及文档自动导入。 --- ## 📖 项目简介 **Genius QA** 是一款专为**政府政策咨询场景**设计的智能问答系统,具备以下核心能力: - **智能语义匹配**:用户即使使用口语化表达(如“退休金怎么领”),系统也能准确匹配到标准政策问答 - **混合检索策略**:结合向量相似度(语义)和全文检索(关键词),双路召回确保高召回率和高精度 - **容错机制**:支持别名(相似问法)、自动降级(向量服务不可用时切换全文检索) - **文档智能导入**:上传 PDF/Word/TXT 文档,系统自动拆解为问答对并入库(基于大模型) - **政府场景适配**:支持政策有效期管理、部门归属、审核状态等政务特有字段 --- ## 🏗️ 技术栈 | 组件 | 技术选型 | 版本 | | ------------------ | ------------------------- | -------- | | **Web 框架** | Goravel | v1.17 | | **数据库** | PostgreSQL | 13+ | | **向量扩展** | pgvector | 0.5.0+ | | **Embedding 服务** | Ollama (nomic-embed-text) | 本地部署 | | **ORM** | GORM (Goravel 内置) | - | | **队列** | Redis (Goravel 内置) | 7.0+ | | **语言** | Go | 1.21+ | --- ## ✨ 功能特性 ### 核心功能 - ✅ **智能问答**:混合检索(向量 70% + 全文 30%),返回最匹配的答案及关联问题 - ✅ **分类过滤**:支持按政策分类(养老、医疗、户籍等)限定检索范围 - ✅ **别名匹配**:用户口语化提问(如“退休金”)自动映射到标准问题 - ✅ **热门问题**:基于 `hit_count` 自动统计最常被问的问题 - ✅ **关联推荐**:每个答案下方附带 3-5 个相关问题,引导用户深入探索 ### 后台管理 - ✅ **问答 CRUD**:增删改查标准问答对,支持批量导入 - ✅ **文档导入**:上传 PDF/Word/TXT,异步解析并拆解为问答对 - ✅ **分类管理**:树形分类体系(支持无限层级) - ✅ **相似问法管理**:为每个问题添加多个口语化变体 ### 技术亮点 - ✅ **向量检索**:基于 pgvector 的 IVFFLAT 索引,百万级数据毫秒响应 - ✅ **降级策略**:Embedding 服务不可用时自动切换纯文本检索 - ✅ **冷热数据**:通过 `status` 字段软删除,通过 `valid_from/valid_until` 实现政策时效管理 --- ## 🚀 快速开始 ### 1. 环境准备 #### 1.1 安装 PostgreSQL 13+ 和 pgvector **macOS (Homebrew)**: ```bash brew install postgresql@13 brew install pgvector ``` **Ubuntu/Debian**: ```bash sudo apt install postgresql-13 postgresql-13-pgvector ``` **Windows**: - 下载 PostgreSQL 13+ 安装包 - 从 [pgvector Releases](https://github.com/pgvector/pgvector/releases) 下载预编译 DLL,复制到 PostgreSQL 的 `lib` 和 `share/extension` 目录 **验证安装**: ```sql CREATE EXTENSION IF NOT EXISTS vector; SELECT * FROM pg_extension WHERE extname = 'vector'; ``` #### 1.2 安装 Ollama 并拉取 Embedding 模型 ```bash # 安装 Ollama (macOS/Linux/Windows WSL2) curl -fsSL https://ollama.com/install.sh | sh # 启动 Ollama 服务 ollama serve # 拉取 Embedding 模型(768 维) ollama pull nomic-embed-text ``` #### 1.3 安装 Go 1.21+ 和 Redis ```bash # Go brew install go # macOS # 或从官网下载: https://golang.org/dl/ # Redis brew install redis redis-server ``` ### 2. 克隆项目并安装依赖 ```bash git clone https://github.com/your-username/genius-qa.git cd genius-qa go mod download ``` ### 3. 配置文件 复制 `.env.example` 为 `.env` 并修改: ```bash cp .env.example .env ``` **关键配置项**: ```env # 数据库 DB_CONNECTION=postgres DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=genius_qa DB_USERNAME=postgres DB_PASSWORD=your_password # Redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= # Ollama OLLAMA_BASE_URL=http://localhost:11434 OLLAMA_EMBEDDING_MODEL=nomic-embed-text ``` ### 4. 创建数据库并执行迁移 ```bash # 创建数据库(PostgreSQL 命令行) psql -U postgres -c "CREATE DATABASE genius_qa;" # 执行迁移 go run main.go artisan migrate ``` ### 5. 填充测试数据 ```bash go run main.go artisan db:seed --seeder=QaSeeder ``` 预期输出: ``` ✅ 生成向量成功 (问题: 养老保险领取需要什么条件?), 维度: 768 插入问答成功: 养老保险领取需要什么条件? (ID: 1) ... Seeder 执行完成! ``` ### 6. 启动服务 **启动 Web 服务**: ```bash go run main.go serve ``` 服务默认运行在 `http://localhost:3000` **启动队列 Worker**(另开终端,用于文档导入等异步任务): ```bash go run main.go artisan queue:work ``` ### 7. 验证服务 ```bash curl -X POST http://localhost:3000/api/qa/ask \ -H "Content-Type: application/json" \ -d '{"question":"养老金怎么领取"}' ``` 预期返回(JSON): ```json { "code": 0, "data": { "answer": "领取养老金需要满足以下条件...", "category": "养老保险", "dept": "人力资源和社会保障局", "score": 0.6542, "related": [...] } } ``` --- ## 📡 API 文档 ### 1. 智能问答 **POST** `/api/qa/ask` **请求体**: ```json { "question": "养老金怎么领取", "category_id": 2 // 可选,指定分类 ID } ``` **响应**: ```json { "code": 0, "message": "success", "data": { "answer": "完整答案文本", "category": "养老保险", "dept": "人力资源和社会保障局", "score": 0.6542, "related": [ {"id": 1, "question": "养老保险领取需要什么条件?", "category_id": 2} ] } } ``` ### 2. 获取分类树 **GET** `/api/qa/categories` **响应**: 树形分类结构(包含父子层级) ### 3. 获取热门问题 **GET** `/api/qa/hot?limit=10` **响应**: 按 `hit_count` 降序排列的问题列表 --- ### 后台管理接口(需添加认证中间件) #### 创建问答 **POST** `/api/admin/qa` ```json { "category_id": 2, "standard_question": "养老保险缴费年限不够怎么办?", "answer": "可以继续缴费至满15年,或转入城乡居民养老保险...", "dept_name": "人力资源和社会保障局", "valid_from": "2020-01-01", "valid_until": "2099-12-31", "aliases": ["缴费年限不足", "养老保险没交够"], "related_qa_ids": ["1", "3"] } ``` #### 上传文档 **POST** `/api/admin/document/upload`Content-Type: `multipart/form-data` - 文件字段名:`file` - 支持格式:`pdf`, `docx`, `doc`, `txt`, `md` #### 查询文档状态 **GET** `/api/admin/document/status/{id}` --- ## 🗂️ 项目结构 ``` genius-qa/ ├── app/ │ ├── http/ │ │ └── controllers/ # 控制器(QA、Admin) │ ├── models/ # 数据模型(Category, QaStandard, QaAlias) │ ├── services/ # 业务服务(Embedding, Search, LLM, Parser) │ ├── jobs/ # 队列任务(文档处理、向量生成) │ └── providers/ # 服务提供者 ├── config/ # 配置文件(database, queue, app) ├── database/ │ ├── migrations/ # 数据库迁移文件 │ └── seeders/ # 测试数据填充(QaSeeder) ├── storage/ # 上传文件存储 ├── routes/ # 路由定义 ├── .env.example # 环境变量示例 ├── go.mod └── main.go ``` --- ## 🧪 测试数据说明 ### 预置分类 | ID | 分类名称 | 父级 | | -- | -------- | ---- | | 1 | 社会保障 | 0 | | 2 | 养老保险 | 1 | | 3 | 医疗保险 | 1 | | 5 | 户籍管理 | 0 | | 8 | 住房保障 | 0 | ### 预置问答 | ID | 问题 | 部门 | 别名 | | -- | -------------------------- | ------------ | ------------------------------ | | 1 | 养老保险领取需要什么条件? | 人社局 | 退休金领取条件、养老金领取资格 | | 2 | 养老金发放时间是什么时候? | 人社局 | 养老金每月几号到账 | | 3 | 医疗保险报销比例是多少? | 医保局 | 医保能报多少 | | 4 | 户口迁移需要哪些材料? | 公安局户籍科 | 迁户口需要什么手续 | | 5 | 公积金贷款额度如何计算? | 公积金中心 | 公积金能贷多少钱 | ### 关联关系 - ID 2 关联到 ID 1 - ID 3 关联到 ID 1 和 ID 2 --- ## 📊 性能优化建议 ### 1. 索引策略 - **向量索引**:当前使用 IVFFLAT,适合百万级数据。数据量超过 100 万时可迁移到 HNSW。 - **全文索引**:已创建 `GIN` 索引,支持中文分词(需 `zhparser` 扩展)。 ### 2. 缓存策略 - **高频问答**:将 `hit_count > 100` 的问题缓存到 Redis,TTL 1 小时。 - **向量结果**:对相同或高度相似的查询,缓存向量检索结果(LRU 策略)。 ### 3. 读写分离 - **主库**:写入(后台管理、文档导入) - **从库**:读取(前台问答) - Goravel 支持多数据库连接,可在 `config/database.php` 中配置。 ### 4. 水平扩展 - **分库分表**:按时间(如 `qa_standards_202501`)或按分类 ID 分表。 - **Goravel 队列**:支持 Redis、RabbitMQ 等多驱动,可横向扩展 Worker 数量。 --- ## 🔬 技术细节 ### 1. 向量化(Embedding) #### 1.1 模型选择 使用 Ollama 本地部署的 `nomic-embed-text` 模型,输出 **768 维**浮点数向量。 #### 1.2 调用流程 ``` 文本输入 → Ollama /api/embeddings → [f32; 768] → pgvector(Vector) → 存入数据库 ``` ```go // EmbeddingService 调用 Ollama 本地 API POST http://localhost:11434/api/embeddings Body: { "model": "nomic-embed-text", "prompt": "用户输入的文本" } Response: { "embedding": [0.123, -0.456, ...] } // 768 维 float32 数组 ``` #### 1.3 存储位置 | 表 | 字段 | 用途 | | -------------- | -------------------------------- | ---------------- | | `qa_standards` | `question_embedding vector(768)` | 标准问题的向量 | | `qa_aliases` | `alias_embedding vector(768)` | 口语化别名的向量 | #### 1.4 生成时机 - **手动创建 QA**:创建后立即异步触发 `GenerateEmbedding` 队列任务生成向量 - **文档导入**:`ProcessDocument` 队列任务中,每条 QA 在入库前同步生成向量 - **别名创建**:`AdminController.CreateQA` 中每个别名异步生成向量 --- ### 2. 查询过程(检索链路) 查询链路采用 **"混合检索 → 别名匹配 → 降级方案"** 三级策略: ``` 用户提问 │ ▼ ┌─────────────────────────────┐ │ Step 1: 混合检索 │ ← 首选策略 │ ① 将用户问题转为 768 维向量 │ │ ② 向量相似度检索 (70% 权重) │ │ ③ 全文检索 (30% 权重) │ │ ④ 加权融合排序 │ └─────────────────────────────┘ │ 有结果 → 返回 │ 无结果 ▼ ┌─────────────────────────────┐ │ Step 2: 别名匹配 │ ← 口语化兜底 │ ① 将用户问题转为向量 │ │ ② 在 qa_aliases 中找最相似别名 │ │ ③ 通过别名找到对应的标准 QA │ └─────────────────────────────┘ │ 有结果 → 返回 │ 无结果 ▼ ┌─────────────────────────────┐ │ Step 3: 纯文本检索 │ ← 最后兜底 │ ① 不使用向量,纯 PostgreSQL │ │ to_tsvector + plainto_tsquery │ │ ② 按 TF-IDF 排序 │ └─────────────────────────────┘ │ 有结果 → 返回 │ 无结果 ▼ 返回"暂无相关答案" ``` #### 2.1 混合检索详解 **向量相似度**(余弦距离): ```sql -- 计算向量相似度:1 - cosine_distance 1 - (question_embedding <=> '<用户问题向量>') AS vector_score -- 过滤阈值:vector_score > 0.5 ``` **全文检索**(PostgreSQL 内置): ```sql -- 使用 simple 分词器(支持中文拼音/英文) to_tsvector('simple', standard_question) @@ plainto_tsquery('simple', '用户问题') -- 计算 TF-IDF 排名 ts_rank(to_tsvector('simple', standard_question), plainto_tsquery('simple', '用户问题')) AS text_score ``` **加权融合公式**: ``` combined_score = vector_score × 0.7 + text_score × 0.3 ``` #### 2.2 别名匹配详解 当用户用口语化表达提问时(如"退休金怎么领"而非标准问题"养老保险领取需要什么条件?"),别名匹配通过向量相似度找到最接近的别名,进而定位到标准 QA: ```sql -- 在 qa_aliases 表中查找最相似的别名 SELECT qa_id, 1 - (alias_embedding <=> '<用户问题向量>') AS score FROM qa_aliases ORDER BY alias_embedding <=> '<用户问题向量>' LIMIT 10; -- 筛选 score >= 0.5 的结果,再关联 qa_standards 获取完整答案 ``` #### 2.3 降级策略 | 场景 | 处理方式 | | ----------------- | ------------------------------------ | | Ollama 服务不可用 | 自动降级为纯全文检索 | | 向量检索无结果 | 尝试别名匹配 | | 别名匹配无结果 | 尝试纯全文检索 | | 全部无结果 | 返回友好提示:"暂时没有找到相关答案" | #### 2.4 分类过滤 用户可通过 `category_id` 限定检索范围,SQL 中通过 `WHERE category_id = ?` 过滤,不影响检索算法本身。 #### 2.5 时效过滤 政策类问答有有效期约束,检索时自动过滤: ```sql AND (valid_from IS NULL OR valid_from <= NOW()) AND (valid_until IS NULL OR valid_until >= NOW()) ``` #### 2.6 命中计数 每次返回最佳答案后,异步递增 `hit_count`,用于热门问题统计: ```go go func() { facades.Orm().Query().Model(&QaStandard{}). Where("id = ?", best.ID). Update("hit_count", gorm.Expr("hit_count + 1")) }() ``` --- ### 3. 文档导入流程 ``` 用户上传/粘贴文档 │ ▼ ┌─────────────────────────────────────────┐ │ Step 1: 文件存储 │ │ • 文件上传 → storage/uploads/documents/ │ │ • 文本导入 → Redis 缓存 │ │ • 创建 import_documents 记录 │ │ • 推送 ProcessDocument 队列任务 │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Step 2: 文档解析 │ │ • PDF → ledongthuc/pdf 提取文本 │ │ • DOCX → nguyenthenguyen/docx 提取文本 │ │ • TXT/MD → 直接读取 │ │ • 内容 < 10 字节 → 标记失败 │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Step 3: LLM 提取问答对 │ │ • 调用 Ollama /api/chat │ │ • Prompt 要求提取: │ │ - question (问题) │ │ - answer (答案) │ │ - parent_category (一级分类) │ │ - child_category (二级分类) │ │ - aliases (口语化别名数组) │ │ • 返回 JSON 数组 │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Step 4: 逐条处理 │ │ ① EnsureCategory → 自动创建分类 │ │ ② GetEmbedding → 生成问题向量 │ │ ③ 查重 → 已存在的跳过 │ │ ④ Create → 存入 qa_standards │ │ ⑤ 遍历 aliases → 生成别名向量并入库 │ │ ⑥ 关联 → 同一文档的 QA 互相建立关联 │ └─────────────────────────────────────────┘ │ ▼ 更新 import_documents 状态为"已完成" ``` #### 3.1 自动分类机制 文档导入时 LLM 自动推断分类,无需预定义分类列表: ``` LLM 输出: parent_category="住房保障", child_category="保障房申请" │ ▼ 查询 categories 表: SELECT * FROM categories WHERE name='住房保障' AND parent_id=0 │ 不存在 → CREATE parent (name='住房保障', parent_id=0) │ 存在 → 使用已有 parent │ ▼ 查询 categories 表: SELECT * FROM categories WHERE name='保障房申请' AND parent_id=? │ 不存在 → CREATE child (name='保障房申请', parent_id=parent.ID) │ 存在 → 使用已有 child │ ▼ 返回 child.ID → 存入 qa_standards.category_id ``` #### 3.2 查重机制 每条 QA 入库前检查 `standard_question` 是否已存在(`status=1`),已存在的跳过,避免重复数据。 #### 3.3 关联关系 同一文档导入的 QA 互相建立 `related_qa_ids` 关联(逗号分隔的 ID 字符串),在问答详情中展示相关问题。 --- ## ⚠️ 常见问题排查 | 问题现象 | 可能原因 | 解决方法 | | ---------------------- | ------------------------------------------------ | ---------------------------------------------- | | `向量维度不匹配` | 数据库`vector(1536)` 与 Ollama 模型 768 维不一致 | 修改迁移文件为`vector(768)`,重新迁移 | | `类型 "vector" 不存在` | pgvector 扩展未安装 | `CREATE EXTENSION vector;` | | `Ollama 连接失败` | Ollama 服务未启动或端口不对 | `ollama serve`,检查 `OLLAMA_BASE_URL` | | `全文检索无结果` | 未安装中文分词扩展 | 安装`zhparser`,或使用 `simple` 配置(已内置) | | `关联问题不显示` | `related_qa_ids` 字段为空或格式错误 | 更新为`"1,2,3"` 格式的字符串 | --- ## 🤝 贡献指南 欢迎提交 Issue 和 Pull Request! 1. Fork 本仓库 2. 创建您的特性分支 (`git checkout -b feature/amazing`) 3. 提交您的修改 (`git commit -m 'Add some amazing feature'`) 4. 推送至分支 (`git push origin feature/amazing`) 5. 创建 Pull Request --- ## 📄 License 本项目采用 **MIT License**,可自由使用、修改、分发。 --- ## 📧 联系方式 - **项目维护者**: [hulu-coder] - **邮箱**: [yuanhaozhuzhu@hotmail.com] - **项目地址**: [https://gitee.com/hulutech/genius-qa.git](https://gitee.com/hulutech/genius-qa.git) --- ## 🙏 致谢 - [Goravel](https://www.goravel.dev/) - 优雅的 Go 语言 Web 框架 - [pgvector](https://github.com/pgvector/pgvector) - PostgreSQL 向量检索扩展 - [Ollama](https://ollama.com/) - 本地大模型部署工具 - [GORM](https://gorm.io/) - Go 语言 ORM 框架 --- ⭐ 如果这个项目对您有帮助,请给个 Star 支持一下!