mirror of
https://github.com/kuaifan/dootask.git
synced 2026-06-15 03:42:13 +00:00
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:
parent
af206480fb
commit
ec1ab31b0e
97
.github/workflows/ai-kb-reindex.yml
vendored
97
.github/workflows/ai-kb-reindex.yml
vendored
@ -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
|
||||
28
CLAUDE.md
28
CLAUDE.md
@ -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-kb(PR 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 测试
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
| DooTask AI 助手灰度配置
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| RAG(帮助知识库检索)功能上线时按以下顺序灰度(参考 plan §八):
|
||||
| RAG(帮助知识库检索)功能上线时按以下顺序灰度:
|
||||
| Stage 1 — staging:RAG_ENABLED=true 仅 staging 环境,全体可用
|
||||
| Stage 2 — canary:RAG_ENABLED=true + RAG_CANARY_USERIDS="1,2,3,4,5"
|
||||
| 仅白名单 user 命中 RAG
|
||||
@ -45,13 +45,4 @@ return [
|
||||
| 有值表示 仅白名单 userid 命中 RAG(Stage 2 canary)。
|
||||
*/
|
||||
'rag_canary_userids' => env('RAG_CANARY_USERIDS', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| RAG 检索语种受控集合
|
||||
|--------------------------------------------------------------------------
|
||||
| 前端可传的 locale 值;不在集合内默认回退到 zh。
|
||||
| P0 仅启用 zh;P1 起开放 en。
|
||||
*/
|
||||
'rag_supported_locales' => ['zh', 'en'],
|
||||
];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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. 提交 PR,CI 会自动跑 lint;通过且 review 完毕后合入 main,CI 自动触发 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` 列认领
|
||||
|
||||
@ -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}`
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user