refactor(ai-kb): 索引同步改为容器启动 reconcile

- 删除 CI reindex workflow(ai-kb-reindex.yml):内容入库不再依赖 CI
  远程触发,AI 插件容器每次启动按文件 hash 对账(reconcile)自动增量
  收敛,免重启即时生效时可手动调 POST /kb/reindex(默认 mode=reconcile)
- ai-kb README.md 同步更新贡献流程与内容同步机制说明
- CLAUDE.md 精简 ai-kb 同步规则:去除与 _schema/README 重复的写作规范
  与背景说明,保留同步时机、操作指引、改完无需触发索引三条
- 另:auth 接口 locale 检索语种缺省改为由请求语言推导(含 zh 视为 zh,
  否则 en),删除 config/ai.php 的 rag_supported_locales 与回退逻辑,
  前端改用 getLanguage() 统一映射,同步更新 ai-kb auth.md
- appstore 镜像升级 0.4.3 -> 0.5.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-06-10 06:59:36 +00:00
parent af206480fb
commit ec1ab31b0e
8 changed files with 17 additions and 147 deletions

View File

@ -1,97 +0,0 @@
name: "AI-KB Reindex"
# 主仓库 ai-kb 内容变更后,远程触发 AI 插件 `POST /kb/reindex` 增量入库。
# 失败不阻塞 PR/发布AI 容器下次启动会自动 ingest_all 兜底plan §三 / R9
on:
push:
branches:
- "pro"
paths:
- "resources/ai-kb/**"
workflow_dispatch:
inputs:
mode:
description: "incremental | full"
required: false
default: "incremental"
paths:
description: "JSON 数组,相对 resources/ai-kb/ 的路径mode=incremental 时使用)"
required: false
default: "[]"
jobs:
reindex:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 5
# 不被其他 job 依赖;失败不阻塞合并。
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # 用于 git diff HEAD~1 HEAD
- name: Resolve changed paths
id: paths
run: |
set -euo pipefail
MODE="${{ github.event.inputs.mode || 'incremental' }}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
INPUT_PATHS='${{ github.event.inputs.paths }}'
if [ -z "${INPUT_PATHS}" ] || [ "${INPUT_PATHS}" = "[]" ]; then
MODE="full"
PATHS_JSON="[]"
else
PATHS_JSON="${INPUT_PATHS}"
fi
else
CHANGED=$(git diff --name-only HEAD~1 HEAD -- 'resources/ai-kb/**/*.md' 'resources/ai-kb/**/*.yaml' 'resources/ai-kb/**/*.yml' || true)
if [ -z "${CHANGED}" ]; then
echo "No ai-kb file changes detected; skip reindex call."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
PATHS_JSON=$(printf '%s\n' "${CHANGED}" \
| sed 's|^resources/ai-kb/||' \
| jq -R . | jq -cs .)
fi
echo "mode=${MODE}" >> "$GITHUB_OUTPUT"
echo "paths_json=${PATHS_JSON}" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
- name: Trigger /kb/reindex
if: steps.paths.outputs.skip != 'true'
env:
AI_SERVICE_URL: ${{ secrets.AI_SERVICE_URL }}
KB_INGEST_TOKEN: ${{ secrets.KB_INGEST_TOKEN }}
MODE: ${{ steps.paths.outputs.mode }}
PATHS_JSON: ${{ steps.paths.outputs.paths_json }}
run: |
set -euo pipefail
if [ -z "${AI_SERVICE_URL:-}" ] || [ -z "${KB_INGEST_TOKEN:-}" ]; then
echo "::warning ::AI_SERVICE_URL or KB_INGEST_TOKEN secret not set; skip remote reindex."
exit 0
fi
BODY=$(jq -cn --arg mode "$MODE" --argjson paths "$PATHS_JSON" '{mode:$mode, paths:$paths}')
echo "Posting body: $BODY"
HTTP_CODE=$(curl -sS -o /tmp/kb-reindex.json -w '%{http_code}' \
--max-time 90 \
--retry 2 --retry-delay 5 --retry-connrefused \
-X POST "${AI_SERVICE_URL%/}/kb/reindex" \
-H "X-Ingest-Token: ${KB_INGEST_TOKEN}" \
-H 'Content-Type: application/json' \
-d "$BODY" || true)
echo "HTTP $HTTP_CODE"
cat /tmp/kb-reindex.json || true
if [ "$HTTP_CODE" != "200" ]; then
echo "::warning ::reindex returned HTTP $HTTP_CODE — AI 容器下次启动会 ingest_all 兜底(参见 plan §三/R9"
exit 0 # 不阻塞 push / 发布
fi

View File

@ -49,31 +49,13 @@ Laravel 8 (LaravelS/Swoole) + Vue 2 (Vite) + Electron。开源任务/项目管
- 新增用户可见文本须追加原文(简体中文)到:前端 `language/original-web.txt`,后端 `language/original-api.txt`(去重)
- 前端翻译用 `$L("文本")`,动态值用 `(*)` 占位:`$L('共(*)条', n)`——禁止拼接翻译
## DooTask AI 知识库 (ai-kb) 同步规则
## ai-kb 同步规则
`dootask/resources/ai-kb/` 是**专给 AI 助手 RAG 检索的功能知识库**,与人类文档(`dootask-website`)独立。它直接影响产品内 AI 助手能否正确回答用户的「X 怎么用」
`resources/ai-kb/` 是产品内 AI 助手 RAG 检索的功能知识库(目录结构、写作规范、索引机制见其 `README.md``_schema/`
**何时必须同步更新 ai-kb**
- 新增、修改、删除任何用户可见的功能、菜单、按钮、流程、字段
- 调整 API 行为(错误码、参数含义、返回结构)
- 引入新插件 / 微应用,或修改权限 / 角色定义
**操作步骤**
1. 在 `resources/ai-kb/_meta/feature-map.yaml` 找到对应 `feature` 的 chunk 清单
2. 按 `resources/ai-kb/_schema/chunk-style.md` 风格修改对应 markdown
3. 更新 frontmatter 的 `last_verified` 字段为当前主程序版本号
4. 该功能没有对应 chunk 时,按 `resources/ai-kb/_schema/frontmatter.md` 规范新建
**禁止**
- 跨章节指代("如上图所示"、"在前面一节"——RAG 切块后会丢失上下文
- 把 dootask-website 的人类教程或截图直接复制过来
- 单独提交「只改 ai-kb」的 PR——应与触发它的主代码改动同一个 PR
- 改产品代码但不改 ai-kbPR review 应拦截)
**自动化**:合入 main 后 CI 自动调用 AI 插件的 `POST /kb/reindex` 增量入库;失败由 AI 容器重启自动 `ingest_all` 兜底。
- **同步时机**:改动用户可见的功能/菜单/按钮/流程/字段、API 行为(错误码、参数含义、返回结构)、插件/微应用、权限/角色定义时,必须在同一次提交中同步更新 ai-kb不要把 ai-kb 改动单独拆成一个提交
- **怎么改**:在 `_meta/feature-map.yaml` 找到对应 feature 的 chunk 清单,按 `_schema/chunk-style.md``_schema/frontmatter.md` 修改或新建 chunk并把 frontmatter 的 `last_verified` 更新为当前主程序版本号
- **改完即止**:无需触发任何索引操作,插件容器启动时会自动对账收敛
## Playwright 测试

View File

@ -32,6 +32,7 @@ class AssistantController extends AbstractController
* @apiParam {String} model_type 模型类型
* @apiParam {String} model_name 模型名称
* @apiParam {JSON} context 上下文数组
* @apiParam {String} [locale] ai-kb 检索语种zh、en缺省取请求语言 language包含 zh 视为 zh否则 en
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -46,12 +47,8 @@ class AssistantController extends AbstractController
$modelType = trim(Request::input('model_type', ''));
$modelName = trim(Request::input('model_name', ''));
$contextInput = Request::input('context', []);
// ai-kb 检索语种;缺省 zh前端传 'zh' / 'en'
$supportedLocales = config('ai.rag_supported_locales', ['zh', 'en']);
$locale = trim(Request::input('locale', 'zh'));
if (!in_array($locale, $supportedLocales, true)) {
$locale = 'zh';
}
$locale = trim(Request::input('locale', '')) ?: trim(Base::headerOrInput('language'));
$locale = str_contains(strtolower($locale), 'zh') ? 'zh' : 'en';
// 灰度判定(参考 config/ai.php总开关 + canary 白名单
$ragEnabled = AI::ragEnabledFor((int) $user->userid);

View File

@ -5,7 +5,7 @@
| DooTask AI 助手灰度配置
|--------------------------------------------------------------------------
|
| RAG帮助知识库检索功能上线时按以下顺序灰度(参考 plan §八)
| RAG帮助知识库检索功能上线时按以下顺序灰度
| Stage 1 stagingRAG_ENABLED=true staging 环境,全体可用
| Stage 2 canaryRAG_ENABLED=true + RAG_CANARY_USERIDS="1,2,3,4,5"
| 仅白名单 user 命中 RAG
@ -45,13 +45,4 @@ return [
| 有值表示 仅白名单 userid 命中 RAGStage 2 canary
*/
'rag_canary_userids' => env('RAG_CANARY_USERIDS', ''),
/*
|--------------------------------------------------------------------------
| RAG 检索语种受控集合
|--------------------------------------------------------------------------
| 前端可传的 locale 值;不在集合内默认回退到 zh。
| P0 仅启用 zhP1 起开放 en。
*/
'rag_supported_locales' => ['zh', 'en'],
];

View File

@ -98,7 +98,7 @@ services:
appstore:
container_name: "dootask-appstore-${APP_ID}"
privileged: true
image: "dootask/appstore:0.4.3"
image: "dootask/appstore:0.5.1"
volumes:
- shared_data:/usr/share/dootask
- ${HOST_DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock

View File

@ -11,7 +11,7 @@ ai-kb/
├── _schema/ 写作规范(必读)
│ ├── frontmatter.md frontmatter 字段规范 + 受控词表
│ └── chunk-style.md chunk 写作风格 + 正反例
├── _meta/ 元数据(CI 与脚本读取)
├── _meta/ 元数据(lint / eval 脚本读取)
│ ├── feature-map.yaml feature 全集 + 每个 feature 的 chunk 清单
│ └── tool-binding.yaml chunk ↔ MCP 工具映射
├── _eval/ 回归测试
@ -46,7 +46,7 @@ ai-kb/
2. 通读 [`_schema/chunk-style.md`](./_schema/chunk-style.md) — 写作风格与正反例
3. 在 [`_meta/feature-map.yaml`](./_meta/feature-map.yaml) 找到对应 feature 的 chunk 清单和归属批次
4. 在对应 `zh/<type>/<feature>/<id>.md` 路径下新建文件
5. 提交 PRCI 会自动跑 lint通过且 review 完毕后合入 mainCI 自动触发 AI 插件的 `POST /kb/reindex` 入库
5. 提交 PR、review 后合入。内容进索引不需要额外操作AI 插件容器每次启动会按文件 hash 对账reconcile自动增量收敛新增/变更/删除;想免重启即时生效可手动调 `POST /kb/reindex`
## 改 DooTask 主程序后必须同步更新这里
@ -64,15 +64,13 @@ volumes:
- ../../../dootask/resources/ai-kb:/app/kb-content:ro
```
触发入库CI 或运维手动)
内容同步机制:容器每次启动按文件 hash 对账reconcile自动增量收敛新增/变更/删除的 markdown——客户实例更新 DooTask 后重启插件容器即生效。需要免重启即时生效时手动触发
```bash
curl -X POST 'http://ai-service/kb/reindex' \
-H "X-Ingest-Token: $KB_INGEST_TOKEN" \
-d '{"paths":["zh/howto/task-create.md"], "mode":"incremental"}'
-d '{"mode":"reconcile"}'
```
容器启动时 lifespan 会自动跑一次 `ingest_all` 作为兜底。
## 维护责任
- **内容**:产品功能负责人 / PM / 技术写作者按 `_meta/feature-map.yaml` 中的 `owner` 列认领

View File

@ -30,7 +30,7 @@ AI 助手发送提问前必须先调用 `POST api/assistant/auth` 生成一次
## 数据流
1. 用户点发送
2. 前端 `POST api/assistant/auth`,参数:`model_type``model_name``context`JSON`locale`zh/en
2. 前端 `POST api/assistant/auth`,参数:`model_type``model_name``context`JSON`locale`zh/en,缺省取请求语言,语言包含 zh 视为 zh否则 en
3. 后端 `AssistantController::auth` 校验登录 + 聊天权限,写入临时凭证
4. 返回 `{stream_key: "xxx"}`
5. 前端开 SSE`ai/invoke/stream/{stream_key}`

View File

@ -241,6 +241,7 @@ import FloatButton from "./float-button.vue";
import AssistantModal from "./modal.vue";
import PromptImage from "./prompt-image.vue";
import {buildWeakPrompt, renderWeakPromptText} from "./page-context";
import {getLanguage} from "../../language";
import {getWelcomePrompts} from "./welcome-prompts";
export default {
@ -1004,9 +1005,7 @@ export default {
* 请求 stream_key
*/
async fetchStreamKey({model_type, model_name, context}) {
// ai-kb 'zh' / 'en' localStorage
const lang = (window.localStorage.getItem('__system:languageName__') || 'zh').toLowerCase();
const locale = lang.startsWith('en') ? 'en' : 'zh';
const locale = /zh/i.test(getLanguage()) ? 'zh' : 'en';
const payload = {
model_type,
model_name,