diff --git a/.claude/agents/quickquip-cr-reviewer.md b/.claude/agents/quickquip-cr-reviewer.md index 3fa71f78..25407aa0 100644 --- a/.claude/agents/quickquip-cr-reviewer.md +++ b/.claude/agents/quickquip-cr-reviewer.md @@ -14,10 +14,17 @@ You are an independent code reviewer for the QuickQuip repository. You did not p - State the reviewed scope and base in the output. - Read the diff and surrounding code. A summary alone is insufficient. +### Release scope + +For a Release, apply the release-specific review in `docs/dev/branching.md`. Record the target-branch or previous-tag base, the frozen development SHA, and the final candidate SHA. Use the cumulative diff to map delivered work and evidence; review the release-preparation diff and relevant integration code directly. Existing PR reviews and checks can bound the review scope. Cumulative size and a deep-review trigger on that cumulative range do not require a full historical line-by-line review. + +Check release metadata and upgrade/rollback instructions against actual configuration, persistence, startup, packaging, and deployment paths. Select the important cross-PR interfaces for this release and state the code and evidence inspected. Expand a module's review when missing evidence, conflicting contracts, or a reproducible failure warrants it. Grade new implementation fixes against their own starting SHA using the normal Standard/Huge rules. Report platform and real-provider checks not performed under Not verified. Special milestones use the additional checklist fixed by the maintainer. + ## Read the contracts first - `CLAUDE.md` and `CONTRIBUTING.md`: repository boundaries, branch rules, secrets, local configuration, and verification. - `docs/dev/README.md`: developer-document ownership and the public/private boundary. +- `docs/dev/documenting.md`: public-document responsibilities, factual accuracy, reader-focused prose, operational examples, maintenance, and review; check preemptive disclaimers and excessive boundary statements by their purpose for the reader. - `docs/dev/style.md`: responsibilities, prohibited god structures, module boundaries, input validation, durable state, and review questions. - `docs/dev/testing.md`: test discipline — admission, assertion basis, antipatterns, and keep/merge/delete rules for test changes. - `docs/dev/architecture.md`: dependency direction and domain ownership. diff --git a/.env.example b/.env.example index 0af68310..ad863c5d 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ PORT=8080 # 容器与进程的时区(日志、定时任务展示时间等),compose 部署默认 Asia/Shanghai。 TZ=Asia/Shanghai ONEBOT_ACCESS_TOKEN=your_token_here -# 本地直跑(非 compose)连本机 OneBot 协议端正向 WebSocket 时取消注释并按实际地址修改;JSON 数组,可配多个。 -# compose 部署由编排注入默认值,无需设置。 +# 本机源码运行时,取消下一行注释并填写 OneBot 正向 WebSocket 地址;JSON 数组支持多个连接。 +# compose 部署由编排提供容器网络中的默认地址。 # ONEBOT_WS_URLS=["ws://localhost:3001/"] QQ_ACCOUNT=your_qq_account_here OPENAI_API_KEY=your_openai_key_here @@ -13,21 +13,21 @@ ANTHROPIC_API_KEY=your_claude_key_here GEMINI_API_KEY=your_gemini_key_here DEEPSEEK_API_KEY=your_deepseek_key_here SEARXNG_BASE_URL=http://127.0.0.1:8888 -# 仅 prod.example 运维模板必填:指向外部 searxng 实例(未设置 compose 启动报错)。 -# docker-compose.example.yml 自包含模板无需设置(内联默认已生效)。 +# prod.example 运维模板使用外部 SearXNG,请填写下面的地址;缺失时 compose 校验失败。 +# 根 docker-compose.example.yml 提供内置 SearXNG 和默认容器地址。 # QUICKQUIP_SEARXNG_BASE_URL=http://searxng:8080 SEARXNG_PUBLIC_BASE_URL=http://127.0.0.1:8888/ SEARXNG_BIND_ADDRESS=127.0.0.1 SEARXNG_BIND_PORT=8888 SEARXNG_SECRET=change-this-secret TAVILY_API_KEY=your_tavily_key_here -# openweather MCP 示例(config/llm.toml.example)引用的天气 API key,启用该示例时填写。 +# 旧 TOML 迁移模板中的 openweather MCP 示例引用此天气 API key;启用该服务时填写。 # OWM_API_KEY= GITHUB_PERSONAL_ACCESS_TOKEN=your_github_pat_here GITHUB_TOOLSETS=context,repos,issues,pull_requests,users,actions GITHUB_READ_ONLY= MCP_ARXIV_PAPERS_MOUNT=arxiv-papers:/root/.arxiv-mcp-server/papers -# prts_wiki MCP 鉴权 token,见 config/llm.toml.example。 +# prts_wiki MCP 鉴权 token,见 config/llm.json.example 的 settings.mcp.servers。 # MCP_PRTS_WIKI_TOKEN= TIEBA_ENABLED=false TIEBA_FORUM_KEYWORD= diff --git a/.gitattributes b/.gitattributes index 5d7cd0dc..b4cec40d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,14 +2,10 @@ # Binary files are auto-detected and never touched. * text=auto eol=lf -# Windows-native scripts must keep CRLF. PowerShell 7+ accepts LF, but we -# keep CRLF explicitly so Windows-only tooling never trips over line endings. -*.ps1 text eol=crlf -*.bat text eol=crlf -*.cmd text eol=crlf - -# Cross-platform production templates use UTF-8 with LF. -prod.example/*.ps1 text eol=lf +# Textual Windows launchers and cross-platform templates use LF. +*.ps1 text eol=lf +*.bat text eol=lf +*.cmd text eol=lf # Image assets *.png binary diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index cd0c3b20..0b41ef73 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -22,8 +22,8 @@ body: - type: textarea id: boundaries attributes: - label: 边界与非目标 / Boundaries & non-goals - description: 范围之外的内容;不应改变的行为。 + label: 使用条件与取舍 / Constraints & trade-offs + description: 影响方案选择的实际条件、目标范围与需要保留的行为。 - type: textarea id: related attributes: diff --git a/.github/ISSUE_TEMPLATE/memo.md b/.github/ISSUE_TEMPLATE/memo.md index c03d4bd0..f1fdee30 100644 --- a/.github/ISSUE_TEMPLATE/memo.md +++ b/.github/ISSUE_TEMPLATE/memo.md @@ -18,15 +18,15 @@ labels: [memo] ## 现象与锚点 / Observations & anchors - + ## 待厘清的设计问题 / Open design questions -## 边界与非目标 / Boundaries & non-goals +## 约束与取舍 / Constraints & trade-offs - + ## 范围与状态 / Scope & status diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md index 69a2f0ed..f060d5a0 100644 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -1,5 +1,5 @@ + +# 请求指纹 Profile + +本文面向需要选择或调整请求模板的部署者。Profile 定义发给上游的客户端名称、请求头与请求体布局。在 Web Admin“LLM 配置 → 渠道”选择 `client_fingerprint`,保存草稿后应用。内置模板如下: + +| ID | 支持协议 | 行为 | +|---|---|---| +| `default` | 四种协议 | Chat 使用 OpenCode 2,Responses 使用 Codex,Anthropic 使用 Claude Code;Gemini 使用 Gemini CLI | +| `quickquip` | 四种协议 | QuickQuip 品牌 UA 与部署、纪元及版本标识,需要显式选择 | +| `opencode2` | OpenAI Chat Completions | OpenCode 2.0.27 native 的 UA、身份头、追踪和 Body 布局;Chat 默认采用 | +| `codex` | OpenAI Responses | Codex TUI 0.162.1 的静态身份、业务 Session 头与缓存键;Responses 默认采用 | +| `claude-code` | Anthropic Messages | Claude Code 2.1.296 Linux CLI 的 Header、身份、`system`、缓存与 Body 布局 | +| `gemini-cli` | Gemini GenerateContent | Gemini CLI 0.64.0 TUI 的 Header、安装 UUID、Session 模型锚点和 Body 布局 | +| `legacy-gemini` | Gemini GenerateContent | 保留通用 Gemini 请求与 API Key 查询参数鉴权,供兼容回退使用 | +| `legacy-claude` | Anthropic Messages | 保留 2.1.150 的既有 Header 与 Body 行为,供兼容回退使用 | + +缺省或选择 `default` 时采用协议默认模板。使用本地 `default.toml` 的部署,整份模板以本地文件为准;升级前核对其中的协议配置及手工 Header/Body 字段。需要通用请求形态时,可选择 `legacy-claude`、`legacy-gemini` 或按下方示例定义自己的模板。 + +JSON 的 `client_fingerprint` 放在渠道对象内;模型参数覆盖不接受该字段。它与 OpenAI Responses 的 `responses_profile` 能力设置分别配置。 + +首次从旧 TOML 迁移且存在受管 Body/Header 冲突时,初始化会先失败;应按[首次迁移的 Profile 字段冲突](llm-configuration.md#首次迁移的-profile-字段冲突)处理迁移输入,再进入 Web 选择兼容 Profile。配置数据库准备完成后,渠道的 Profile 选择与手工字段调整可在同一草稿中保存并应用。 + +## 自有品牌请求头 + +默认 UA 为 `QuickQuip/<实际安装包版本>`,例如 `QuickQuip/1.16.2.dev1`。版本来自已安装 Python 包的元数据,缺少包元数据时使用 `unknown` 并记录诊断。Provider 参数 `user_agent` 优先于附加请求头中的 UA,附加 UA 优先于 Profile 默认 UA;请求头名称按大小写无关处理。 + +| 请求头 | 来源及生命周期 | +|---|---| +| `x-quickquip-install` | 首次使用时计算随机 UUID 字符串的 SHA-256,保存到 `data/client_identity.json`;跨重启稳定 | +| `x-quickquip-session` | 当前请求冻结的纪元 UUIDv4;纪元实际推进时改变,重启建立新纪元 | +| `x-quickquip-cluster` | 全局 `settings.runtime.client_cluster`;空字符串时省略 | +| `x-quickquip-client` | 实际安装包版本,与默认 UA 的版本来源相同 | + +四个专有头仅在 `quickquip` Profile 下发送。Provider 的 `headers` 不接受这四个名称,包括不同大小写拼写;配置校验与运行时均拒绝手工覆盖。 + +cluster 在 Web 的全局设置中填写,部署内所有渠道共用,只使用可打印 ASCII。安装标识随数据卷备份恢复;复制数据卷会保留相同安装身份。创建独立部署时,在停止服务并保留原文件备份后,移走新部署的 `data/client_identity.json`,首次使用会生成新身份。损坏的身份文件会明确报错,需要管理员检查和恢复。 + +业务 Session 与纪元分别管理。业务 Session 标识持续对话,纪元标识当前上下文窗口;冷场、预算裁剪等窗口推进可以改变纪元并保留业务 Session。请求冻结纪元后,重试、备用地址和工具循环复用该值;辅助识图继承调用时的纪元。独立任务在每次操作开始时生成临时纪元,操作内部级联和重试复用。 + +Anthropic 的 `quickquip` Profile 移除默认 Claude Code 客户端身份头、Stainless 头、浏览器访问标记和 `claude-code` beta 项。其余现有能力声明、URL 与请求 Body 保持原有行为;Claude Code Body 对齐由对应客户端策略负责。 + +## 编辑 Profile 文件 + +内置 TOML 随包分发,源码位置为 `src/quickquip/llm/client_fingerprints/defaults/`。将需要调整的文件复制到 `config/client_profiles/`,保留文件名与 `id`,编辑后重启 Bot 和 Web。相同 ID 使用本地文件整份覆盖;新增 ID 可定义独立 Profile。文件修改不会随配置“应用”自动重新加载,正在执行的操作继续使用其配置快照。 + +Profile 的最小文件示例: + +```toml +format_version = 1 +id = "custom" +label = "Custom Client" +user_agent = "CustomClient/{client_version}" + +[headers] +accept = "application/json" + +[protocols.openai] +[protocols.openai_responses] +``` + +文件名为 `custom.toml`。`protocols` 声明支持的协议,协议表可包含 `headers` 和 `bindings`。根表中的请求头用于全部已声明协议;协议表补充本协议的请求头,同一名称不能重复声明。 + +`user_agent` 支持 `{client_version}` 版本替换。动态 `bindings` 可读取以下来源;按需要在协议表中声明绑定: + +| 来源 | 含义 | +|---|---| +| `install_id`、`epoch_id`、`cluster_id`、`client_version` | QuickQuip 品牌头的安装、纪元、部署群与版本值,归属 `quickquip` Profile | +| `stainless_os` | 当前运行环境的操作系统标识 | +| `business_session_id` | 业务 Session 的规范 UUID | +| `opencode_session_id`、`project_id` | OpenCode 会话与部署项目标识 | +| `b3`、`traceparent` | 每次实际 HTTP 尝试的追踪头 | +| `timeout_seconds`、`retry_count` | 渠道超时秒数与当前重试策略轮次 | +| `gemini_cli_install_id` | Gemini CLI 安装 UUID,适用于 Gemini 协议 | + +`quickquip` Profile 须保留四个专有头及对应绑定。`authorization`、`x-api-key`、`x-goog-api-key` 和 `content-type` 由协议适配器管理,在 Profile 的静态头或绑定中填写这些名称会使加载校验失败。渠道认证字段使用根 `.env` 的变量引用。 + +协议表可使用 `protocol_ref = "claude-code"` 引用另一 Profile 的同协议配置。引用表只包含此字段,目标使用协议局部配置,顶层 UA、Header 和 `bindings` 为空;禁止链式及循环引用。内置 `default` 的 Chat、Responses、Anthropic 和 Gemini 分别引用 `opencode2`、`codex`、`claude-code` 和 `gemini-cli`。客户端模板的 UA 与其他特征定义在协议表中;已有 `opencode2`/`codex` 本地副本需将顶层 `user_agent` 移入对应 `protocols.<协议>.headers` 的 `user-agent` 字段,满足引用目标的协议局部约束;修改对应本地模板后重启,`default` 与显式客户端选择共用同一策略快照。 + +解析器拒绝未知字段、非法动态来源、重复头和不支持的协议。Profile 文件不保存密钥,不执行表达式或脚本。附加认证配置继续使用根目录 `.env` 的变量引用。 + +源码部署、wheel 和容器均包含内置模板。部署脚本保留当前 release 的本地覆盖文件,新候选中已有的同名文件优先;请同时备份覆盖目录与数据卷。 + +## OpenCode 2 Profile + +选择 `opencode2` 后,默认 UA 为 `opencode/latest/2.0.27/cli`,`client` 为 `cli`;四个 OpenCode Session/affinity 头使用同一业务 Session 投影。新建或清空会话改变身份;冷场、历史裁剪和纪元推进保持身份。投影为 `ses_` 加 26 字符,稳定派生自业务 UUID 和创建时间,恢复业务 Session 后获得相同值。没有父子会话关系时省略 `parent` 头,默认不发送 `x-opencode-request`。 + +`project` 使用部署内稳定的不透明 40 位标识,从 `data/client_identity.json` 的安装身份域分离派生,不包含群号、用户号或本机路径。此 Profile 不发送四个 `x-quickquip-*` 品牌头。 + +`b3` 和 `traceparent` 用于关联模型调用及每次 HTTP 尝试:同一进程共享追踪标识,每次模型调用建立父级记录,每次尝试生成独立记录。并发、工具续轮、重试和备用地址各自保留对应上下文;这些追踪值只在进程内使用,业务 Session 身份按会话生命周期管理。 + +自动绑定的 Session、`project` 和追踪头禁止在渠道 `headers` 中手工覆盖,大小写变体同样拒绝;UA 继续采用本页的覆盖优先级。Profile 文件本身可以编辑其绑定和策略,修改后重启生效。 + +通用兼容端点的 Body 使用 `max_completion_tokens`、`store=false`、工具 `strict=false`,省略默认 `tool_choice=auto`。公开端点的兼容规则来自固定版本源码,保存在 TOML 中,按实际候选 URL 的主机及渠道 ID 匹配;条件分别控制 输出 token 字段、`store`、`strict`、`usage` 和 `tool_stream`。代理端点使用自身的匹配结果,模型别名不会自动推断后台供应商。 + +输出预算、温度、思考档位、人格和工具权限使用当前业务配置。工具返回省略冗余 `name`,空 `assistant` 工具正文使用 `null`;工具顺序、调用 ID、参数、结果、思考和图片继续保留。有工具历史而无当前工具时发送 `tools=[]`。显式 `extra_body` 优先于 Profile 缺省;同时配置 `max_tokens` 和 `max_completion_tokens` 会报错。手工覆盖可改变最终特征,管理员需要按上游协议填写。 + +Body 在确定实际候选 URL、流式模式之后投影,以紧凑 UTF-8 JSON 发送。流式请求保留 `Accept=*/*`,声明 `gzip/deflate/br/zstd` 并具备对应解压支持。`quickquip` 默认声明 `gzip/deflate`;`default` 的 Chat 使用同一 OpenCode 压缩策略。渠道的非流式配置及流式失败降级按既定策略执行。 + +协议表可增加 `body` 策略和 `header_order`;Chat Body 支持 `openai-chat-native`,字段及端点规则示例见随包 `opencode2.toml`。策略和协议不匹配、错误类型、未知策略或未知字段会在加载阶段拒绝。 + +Header 布局在 HTTPX 补齐 `Host`、`Connection`、`Content-Length` 后应用,保留协议鉴权和值,按文件声明调整名称大小写及顺序;额外 Header 保留在声明字段之后。HTTP/1.1 实际序列化时,HTTPX 的 h11 传输层将 `Host` 放在首位,其他字段保持模板顺序和大小写。Trace 记录应用构造的请求头与值;实际传输时的名称和排列按模板及上述传输规则确定。 + +完整模板记录固定客户端版本和源码基线,升级目标版本时同步维护模板、证据和验证。 + +## Claude Code Profile + +Anthropic 的内置 `default` 和 `claude-code` 使用固定 2.1.296 Linux x64 CLI、SDK 0.128.0、runtime v26.3.0、API Key、attribution 关闭的基线。流式与非流式均发送 `Accept=application/json`,支持 `gzip/deflate/br/zstd`。`X-Stainless-Timeout` 跟随渠道 `timeout_seconds`;`Retry-Count` 从 0 开始,按现有重试策略递增,备用地址保持当前轮次,新调用与流式降级从 0 开始。实际网络超时和流式读超时继续使用渠道策略。 + +`system` 为可编辑 CLI 身份文字和原有 QuickQuip 业务指令两个 `text` 块。人格、认人、引用、艾特和工具权限由业务 `system` 保持。模型、输出预算、`thinking`、工具集合、历史签名和媒体使用实际业务数据;转换只操作发送副本。 + +`X-Claude-Code-Session-Id` 与 `metadata.user_id.session_id` 共用业务 Session UUID;冷场、裁剪、纪元推进、重启及恢复保持,新建或清空改变。`user_id` 是紧凑 JSON 字符串,键序为 `device_id`、`account_uuid`、`session_id`。API Key 基线的 `account_uuid` 为空,当前不发送 Agent/parent-agent ID 或 billing 连续性字段。 + +全局 `settings.runtime.claude_code_device_id` 可在 Web Admin 设置,所有使用 CC 策略的渠道共用。非空值须为 64 位十六进制,保存时规范为小写;可填写已有 CC 的 userID。使用现成身份时,同步调整模板 OS/arch 以对应其来源环境。该值随配置修订保存和发布。 + +留空时首次使用生成随机 32 字节,并将 64hex 自动身份保存到 `data/client_identity.json` 的 `claude_code_device_id` 字段。身份文件保留已有安装标识;文件损坏时启动或发送会报错,需要恢复备份。手工设置不覆盖自动值,清空设置恢复自动身份。备份恢复和部署迁移保留身份;复制数据卷建立独立部署时,可填写独立值,或停止服务、备份后移除新副本的自动 CC 字段再启动。该随机标识用于区分客户端安装;API 鉴权使用渠道配置的凭据。 + +有效 `thinking` 开启时,缺省 `context_management` 保留所有 `thinking` 块;显式 `extra_body.context_management` 优先。beta 根据最终 `tools`、`thinking`、`effort` 和 JSON schema 输出派生;`context-1m` 使用已知模型窗口大于模板阈值的工程判据,窗口未知时省略。阈值默认 200000,可在 TOML 调整;实际上下文容量以模型配置与上游支持为准。显式 `anthropic-beta` Header 覆盖继续优先。不自动声明 `advanced-tool-use`、DTU 或 AFK。 + +缓存开启时在两个 `system` 块及末消息最后可缓存块打点,`tools` 不打点;TTL 沿用渠道设置。缓存关闭时清除协议位置的标记。`thinking` 块不直接打点,最终最多四个断点;工具参数、结果正文和 schema 保持不变。新增身份文字及结构开销计入首轮和工具循环预算,预算中的模型与输出预留采用最终覆盖值。 + +CC Body 策略拒绝渠道和模型 `extra_body` 中的 `system`、`messages`、`tools`;自动 `user_id` 开启时拒绝 `metadata.user_id`,包括 `null`。`metadata` 需为对象,其他键保留。动态 Session Header 禁止手工覆盖;UA 继续使用原有优先级。需要手工 `user_id` 时,在本地 Profile 将 `metadata_user_id` 设为 `false`,并移除对应 Session Header binding;需要完整手工 Body 时使用 `legacy-claude` 或不声明自动 Body 策略的自定义 Profile。 + +`claude-code.toml` 包含 `claude-code-messages` 策略、条件 beta 和 `stream_accept = "preserve"`。协议表可设置 `stream_accept` 为 `preserve` 或 `event-stream`,分别保留已有 Accept 或为流式请求设 `text/event-stream`。Body 以紧凑 UTF-8 JSON 发送。原有 `default.toml` 本地覆盖继续整份生效;内置默认升级后出现受管字段冲突时,在应用前调整渠道配置或选择 `legacy-claude`。 + +## Codex Profile + +选择 `codex` 后,默认 UA 为 `codex-tui/0.162.1 (Ubuntu 24.04; x86_64) unknown (codex-tui; 0.162.1)`,`originator` 为 `codex-tui`。它是固定的 Linux 环境预设,版本与环境描述均保存在 TOML;需要匹配另一环境时编辑覆盖文件并重启。UA 继续采用本页的覆盖优先级,压缩声明为 `gzip, deflate`。 + +`session-id`、`thread-id`、`x-client-request-id` 及 Body 的 `prompt_cache_key` 使用同一个业务 Session UUID。重试、工具续轮、冷场、历史裁剪及纪元推进保持;新建或清空业务 Session 后改变,恢复存档后沿用原身份。`x-client-request-id` 使用线程作用域。QuickQuip 当前没有独立子线程身份,三个请求头保持同值,直接使用已有 UUIDv4。 + +动态身份头拒绝渠道 `headers` 手工覆盖,包括大小写变体。Body 绑定生效时,渠道及模型的 `extra_body` 均禁止包含 `prompt_cache_key`,包括 `null`;配置校验和运行时会明确报错。需要手工控制缓存键时,从本地 Profile 文件移除该绑定并重启。标准渠道/模型 `extra_body` 中的 `prompt_cache_key` 属于缓存亲和数据,未声明动态绑定的 Profile 允许手工填写;`default` 的 Responses 和 `codex` 共同适用上述覆盖保护。其他位置和认证字段继续执行原有凭证校验。 + +可选协议表 `body_bindings` 当前仅支持 OpenAI Responses 的以下绑定: + +```toml +[protocols.openai_responses.body_bindings] +prompt_cache_key = "business_session_id" +``` + +投影在实际 HTTP 尝试边界读取冻结请求,对载荷副本赋值;未知字段、来源、类型或协议组合会在加载阶段拒绝。Profile 的 `format_version` 为 `1`。 + +## Gemini CLI Profile + +Gemini 的内置 `default` 与 `gemini-cli` 采用 Gemini CLI 0.64.0、JavaScript SDK 1.30.0、Node 24.18.0、Linux x64 terminal TUI 的 HTTP/1.1 基线。版本、环境、静态头、Header 次序和 Body 布局保存在 `gemini-cli.toml`,支持现有本地覆盖。`Accept` 为 `*/*`,流式请求保持此值;压缩声明 `br, gzip, deflate`,使用发行物已有 Brotli 解码依赖。 + +协议局部 `user_agent_template` 支持 `{session_model}`,默认 UA 为 `GeminiCLI-tui/0.64.0/{session_model} (linux; x64; terminal)`。业务 Session 首次使用该模板时,冻结实际请求的模型名;之后切换模型或渠道、工具续轮、重试及纪元推进沿用原锚点。`default` 与显式 `gemini-cli` 共用锚点。重启、聊天存档与恢复保留;清空或新开 Session 后重新绑定。模板版本和环境使用当前配置快照,显式 UA 覆盖优先级沿用本页约定。 + +现有会话在首次使用此模板时绑定模型名,并保存到会话数据。临时总结、诊断等操作的锚点仅在该 Session 内存中保持;辅助识图继承传入 Session。模型名在当前业务 Session 中固定。API Key 路径通过本节列出的安装请求头传递身份。 + +全局 `settings.runtime.gemini_cli_install_id` 可在 Web 设置,为可选 UUIDv4,保存时规范为小写。留空时首次发送安装绑定生成独立随机 UUIDv4,保存在 `data/client_identity.json` 的 `gemini_cli_install_id` 字段,跨进程与重启稳定。手工设置不修改自动值,清空设置恢复自动值;损坏的已存身份会报错。`x-gemini-api-privileged-user-id` 使用此值,仅在声明绑定的 Profile 下发送,渠道 `headers` 禁止覆盖;需要省略时,从本地模板移除该绑定。它表达安装维度身份,随数据卷备份恢复,独立部署应使用独立身份。该值随配置修订保存和发布。 + +协议局部 `api_key_transport` 支持 `query` 或 `header`。新模板选择 `header`,由适配器注入 `x-goog-api-key`,不将凭据放入 URL;显式 `auth_method=bearer` 保持 `Authorization: Bearer`。认证字段不接受 Profile 静态配置。`legacy-gemini` 和未声明该设置的自定义 Profile 使用既有查询参数传输。 + +`gemini-cli-generate-content` 策略保留当前人格和业务指令,发送为单个 `text` 的 `systemInstruction`,并设置 `role=user`。工具声明使用 `parametersJsonSchema`,保留实际 JSON Schema、工具及权限。输出预算、采样、`thinking`、媒体、工具 ID 与原生 `thoughtSignature` 使用业务数据。结构重排作用于发送副本,数组顺序和任意工具参数内容保留,JSON 为紧凑 UTF-8 且无末尾换行。 + +`contents`、`systemInstruction`、`tools` 为策略管理字段,渠道及模型 `extra_body` 中包含这些字段时,在保存和发送阶段拒绝;其余覆盖保持现有语义。启用新默认前核对受管字段与供应商 JSON Schema 支持,必要时选择 `legacy-gemini`。已有本地 `default.toml` 继续整份覆盖生效。 diff --git a/skills.example/self-docs/references/docs-admin-configuration.md b/skills.example/self-docs/references/docs-admin-configuration.md index dc5a6b66..738783f0 100644 --- a/skills.example/self-docs/references/docs-admin-configuration.md +++ b/skills.example/self-docs/references/docs-admin-configuration.md @@ -2,7 +2,9 @@ # QuickQuip 配置参考 -本文档列出 QuickQuip 主要可配置项,按文件和作用域分类。 +LLM 配置示例为导出 JSON 中的字段片段;请在 Web Admin“LLM 配置”中编辑,或修改完整导出文件后预览导入、保存草稿并单独应用。完整交换格式见 [LLM 配置管理](llm-configuration.md)。 + +本文面向部署者,按配置文件与作用范围列出可配置项。环境变量在根 `.env` 中维护并通过重启生效;LLM 草稿保存后单独应用;独立 TOML 文件的生效方式见对应部署与管理说明。 --- @@ -32,7 +34,7 @@ | `GITHUB_TOOLSETS` | GitHub MCP 启用的工具集,逗号分隔。可选:`context`, `repos`, `issues`, `pull_requests`, `users`, `actions` | | `GITHUB_READ_ONLY` | 设为非空时限制 GitHub MCP 为只读 | | `TAVILY_API_KEY` | Tavily API key(供 MCP sidecar 的 Tavily 工具使用,未启用 MCP Tavily 时无需填写) | -| `MCP_PRTS_WIKI_TOKEN` | prts_wiki MCP 的鉴权 token(见 `config/llm.toml.example` 的 prts_wiki 示例) | +| `MCP_PRTS_WIKI_TOKEN` | prts_wiki MCP 的鉴权 token(见 [MCP 接入指南](mcp-servers.md) 的 prts_wiki 示例) | ### 搜索 @@ -79,7 +81,7 @@ LLM 工具 `search_web` 与 `/search` 命令固定走项目内 SearXNG。普通 |------|------| | `MCP_ARXIV_PAPERS_MOUNT` | arXiv MCP server 论文保存卷挂载,格式 `host-path:container-path`。默认 `arxiv-papers:/root/.arxiv-mcp-server/papers` | -其他 `${ENV_VAR}` 与 `${ENV_VAR:-default}` 语法在 `config/llm.toml` 的 MCP server 配置中均可用。 +MCP server 的普通字段支持 `${ENV_VAR}` 与 `${ENV_VAR:-default}`;认证字段使用 `${ENV_VAR}` 保存引用,密钥值维护在根 `.env`。 ### Web Admin @@ -99,22 +101,29 @@ LLM 工具 `search_web` 与 `/search` 命令固定走项目内 SearXNG。普通 | `PIP_TRUSTED_HOST` | pip 信任主机 | | `PLAYWRIGHT_BASE_IMAGE` | Playwright 基础镜像(可含国内代理前缀) | -GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜像构建,并内置 Docker CLI,便于贴吧采集和可选 MCP docker transport 使用。docker transport 仍需显式挂载宿主机 Docker socket,默认 compose 不会挂载。 +GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜像构建,并内置 Docker CLI,便于贴吧采集和可选 MCP docker transport 使用。使用 docker transport 时须自行挂载宿主机 Docker socket;默认 compose 保持该 socket 未挂载。 --- -## config/llm.toml +## LLM 配置数据库 + +`data/llm_config.db` 是运行时的配置来源,JSON 是配置交换格式。通过 Web Admin“LLM 配置”或 `python -m quickquip.llm.config_cli` 修改草稿,再单独提交应用;Bot 启动加载已发布快照。`draft_revision`、`published_revision` 与 Bot 回报的 `loaded_revision` 分别表示草稿、发布和实际运行版本。旧 `config/llm.toml` 只作为首次迁移输入及 1.16.1 回退资产保留。完整流程、备份与回退说明见 [LLM 配置管理](llm-configuration.md)。 -### `[runtime]` — 运行参数 +下文路径对应完整 JSON 文档,认证字段只保存根 `.env` 的变量名或引用。字段范围、枚举和继承能力由 `/ops/api/llm-configuration/metadata` 提供;新增写入拒绝非法值。 + +### `settings.runtime` — 运行参数 | 键 | 说明 | 默认值 | |----|------|--------| -| `enabled` | 全局 LLM 开关(`llm.toml.example` 中显式设为 `true`) | `false` | +| `enabled` | 全局 LLM 开关(`config/llm.json.example` 中显式设为 `true`) | `false` | | `memory_enabled` | 全局记忆注入开关 | `true` | | `default_provider` | 默认 provider ID | — | | `default_persona` | 默认人格 ID | — | -| `history_limit` | 单次调用读取的对话行数兜底——**自 1.14 起语义变更**:不再默认生效(默认路径由会话纪元自动管理);仅当某会话通过 `/llm context_limit ` 显式覆盖时(群聊/私聊均可,上限 1024 条),该会话退化为保留最新 n 行的滚动窗 | `10` | -| `history_max_messages_per_group` | **自 1.14 起废弃(保留解析、不再生效)**:存储裁剪由会话纪元锚点驱动,硬上限统一为 2048 行(`service_parts/constants.py`) | `40` | +| `client_cluster` | 可选部署群标识,仅使用可打印 ASCII;由 QuickQuip Profile 投影为品牌请求头,见[请求指纹 Profile](client-fingerprints.md) | `""` | +| `claude_code_device_id` | 可选 Claude Code 设备身份,填写 64hex 字符串;留空使用数据目录中的持久随机身份 | `""` | +| `gemini_cli_install_id` | 可选 Gemini CLI 安装 UUIDv4;留空使用持久随机身份,见[请求指纹 Profile](client-fingerprints.md) | `""` | +| `history_limit` | 兼容字段,解析但不参与窗口计算。默认由会话纪元自动管理;群聊或私聊通过 `/llm context_limit ` 设置固定窗口时,保留最新 n 行,上限 1024 条 | `10` | +| `history_max_messages_per_group` | 兼容字段,解析后忽略其值。对话存储按会话纪元锚点裁剪,硬上限 2048 行 | `40` | | `memory_limit` | 单次调用注入的记忆条数上限 | `6` | | `memory_max_items_per_group` | 单群存储的记忆条数硬上限 | `200` | | `max_prompt_chars` | system prompt 最大字符数 | `4000` | @@ -135,22 +144,22 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 | `epoch_cap_tokens` | 窗口硬上限:超过即触发触顶重置(H_hot / cap) | `64000` | | `recent_context_token_budget` | 【现场】补丁每轮 token 预算:近期消息缓冲服役给 LLM 的上限(从最新往回截) | `800` | | `recent_context_floor_seconds` | 【现场】滑动保底窗秒数:窗内消息即使已服役过也会重附(增量语义之外的保底) | `300` | -| `request_input_token_budget` | 实际请求输入预算(应用侧估算口径,非模型平台上限):按「provider 显式覆盖 > 模型窗口推导(窗口−输出预留)> 本缺省」解析,超限时主链先把纪元窗口缩到热水位重建请求重试一次,仍超限才拒绝发起;工具调用循环内的逐轮门禁超限立即中止该循环(无降级重试);可在 `[[providers]]` 段按 provider 覆盖(非正数视为未配置) | `96000` | +| `request_input_token_budget` | 实际请求输入预算(应用侧估算口径,非模型平台上限):按“provider 显式覆盖 > 模型窗口推导(窗口−输出预留)> 本缺省”解析,超限时主链先把纪元窗口缩到热水位重建请求重试一次,仍超限才拒绝发起;工具调用循环内的逐轮门禁超限立即中止该循环(无降级重试);可在渠道或模型的 `parameters` 覆盖(null 表示继承) | `96000` | | `agent_record_retention_days` | 已关闭对话轮(Loop)保留天数,按关闭时间计 | `30` | | `agent_record_max_loops_per_scope` | 每会话已关闭 Loop 数量上限,先触顶者触发清理最旧完整 Loop | `1000` | | `agent_record_max_bytes_per_scope` | 每会话 Loop 业务记录字节上限(UTF-8 计量) | `67108864` | -| `agent_replay_loop_tokens` | 历史 Loop 重放投影预算的推导下限(token 估算,512-4194304):实际预算按「请求输入预算 − 纪元可见窗上限 − system/工具预留 − 当前 Loop 比例预留」推导(配置了模型容量的 provider 自动放大到窗口量级),超限按固定阶梯确定性精简(先剥原生 thinking,再丢原生副本,再收工具结果);可在 `[[providers]]` 段按 provider 硬覆盖 | `4096` | +| `agent_replay_loop_tokens` | 历史 Loop 重放投影预算的推导下限(token 估算,512-4194304):实际预算按“请求输入预算 − 纪元可见窗上限 − system/工具预留 − 当前 Loop 比例预留”推导(配置了模型容量的 provider 自动放大到窗口量级),超限按固定阶梯确定性精简(先剥原生 thinking,再丢原生副本,再收工具结果);可在渠道或模型的 `parameters` 中硬覆盖 | `4096` | | `agent_delivery_intermediate_enabled` | 中间轮交付的全局默认:开启后工具调用多轮回复中非最终轮的普通正文照常先于工具执行外发;关闭时非最终正文只记录不发送(`suppressed_by_policy`) | `false` | -| `agent_delivery_final_enabled` | 最终轮分段的全局默认:开启后最终正文按自然段拆成多条消息经 sink 外发;关闭时最终正文沿旧单发路径整条发送。两域独立,旧键 `agent_delivery_enabled` 未删除,读取时按两域同值映射。各群/私聊会话可用 `/llm delivery intermediate/final/all …` 或 Web Admin「群设置」按会话覆盖 | `false` | +| `agent_delivery_final_enabled` | 最终轮分段的全局默认:开启后最终正文按自然段拆成多条消息发送;关闭时最终正文整条发送。两域独立;旧迁移输入中的 `agent_delivery_enabled` 按两域同值转换。各群/私聊会话可用 `/llm delivery intermediate/final/all …` 或 Web Admin“群 LLM”按会话覆盖 | `false` | | `reply_split_threshold_chars` | 回复超过该长度(Unicode code point)才进行自然分段 | `800` | | `reply_chunk_max_chars` | 单段源文本上限,独立于 OneBot 协议报文长度 | `1200` | | `reply_send_interval_ms` | 同会话相邻发送开始时间的最小间隔(0-10000) | `800` | | `reply_max_chunks_per_loop` | 单次对话交付条目上限(含文字、媒体与通知,1-256) | `64` | -| `mention_cooldown_seconds` | 出站「@名字」艾特的同目标冷却秒数:窗内重复艾特同一人降级为纯文本(0 关闭冷却,0-86400) | `600` | +| `mention_cooldown_seconds` | 出站“@名字”艾特的同目标冷却秒数:窗内重复艾特同一人降级为纯文本(0 关闭冷却,0-86400) | `600` | -以上 6 个 `epoch_*` 键均可在 `[[providers]]` 条目里同名覆盖(如 DeepSeek 的缓存存活更久,`epoch_cold_idle_seconds` 可放宽到 `21600`);未覆盖的键继承 `[runtime]` 值。参数关系需满足 `0 < cold_target < cold_trigger ≤ hot_target < cap` 且 `context_tokens > 0`,非法时回退并记 warning。`recent_context_*` 两键仅全局,不支持 provider 覆盖。 +以上 6 个 `epoch_*` 键均可在渠道或模型的 `parameters` 对象内同名覆盖(如 DeepSeek 的缓存存活更久,`epoch_cold_idle_seconds` 可放宽到 `21600`);未覆盖的键继承 `settings.runtime` 值。参数关系需满足 `0 < cold_target < cold_trigger ≤ hot_target < cap` 且 `context_tokens > 0`,新写入的非法组合由校验拒绝。`recent_context_*` 两键仅全局,不支持 provider 覆盖。 -### `[triggers]` — 触发方式 +### `settings.triggers` — 触发方式 | 键 | 说明 | 默认值 | |----|------|--------| @@ -159,14 +168,14 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 | `allow_at` | 启用艾特触发 | `true` | | `empty_prompt_reply` | 空提示时的默认回复文本 | `请在触发指令或艾特后面补上想说的话。` | -`[triggers.auto_search]` — 自动联网判定: +`settings.auto_search` — 自动联网判定: | 键 | 说明 | 默认值 | |----|------|--------| | `enabled` | 是否启用自动联网 | `false` | | `search_max_calls_per_round` | 单轮最大搜索调用数,范围 1-32 | `3` | -`[triggers.quick_judge]` — 快速判定模型: +`settings.quick_judge` — 快速判定模型: | 键 | 说明 | 默认值 | |----|------|--------| @@ -177,22 +186,22 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 快速判定用于 `context_rules` 的 `llm_context`、唤醒模块的相关性/答疑判定等短 prompt 场景。技术失败(超时、provider 异常、空正文、截断、无效 JSON)一律按未触发处理(fail-closed),不会写入 60 秒判定缓存;只有成功解析的业务 true/false 会进缓存。选用带 reasoning 的模型时,reasoning token 计入 `max_tokens` 且延迟更高,需要同时调大 `max_tokens`(如 256)与 `timeout`(如 6 秒),否则会出现“预算被思考耗尽、可见判定为空”与大面积超时。 -### `[image_preprocessing]` — 非视觉模型图片转述 +### `settings.image_preprocessing` — 非视觉模型图片转述 -当当前主模型出现在所属 provider 的 `non_vision_models` 中时,运行时先调用指定视觉模型,将图片转成带来源和序号的文本,再交给主模型。视觉主模型直接接收原图,不调用该前置层。 +当当前主模型条目的 `vision` 为 `false` 时,运行时先调用指定视觉模型,将图片转成带来源和序号的文本,再交给主模型。视觉主模型直接接收原图,不调用该前置层。 | 键 | 说明 | 默认值 | |----|------|--------| | `enabled` | 启用非视觉模型图片转述 | `false` | | `provider_id` | 提供视觉识别能力的 provider ID | `""` | | `model` | 视觉模型 ID;留空使用该 provider 的默认模型 | `""` | -| `max_tokens` | 单张图片转述输出上限,运行时限制为 80-2048 | `300` | +| `max_tokens` | 单张图片转述输出上限,写入范围为 80-2048 | `300` | | `temperature` | 图片转述温度 | `0.3` | | `prompt` | 自定义转述 system prompt;留空使用内置提示 | `""` | 单轮最多处理 5 张当前、引用或转发图片。被动唤醒需要近期图片时,会用剩余名额选择最新图片。任一图片转述失败时,本轮不会调用非视觉主模型,用户会收到可重试提示。 -### `[tools]` — 工具调用 +### `settings.tools` — 工具调用 | 键 | 说明 | 默认值 | |----|------|--------| @@ -201,8 +210,8 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 | `discovery_mode` | 工具发现模式:`off` 全量暴露;`on` 仅暴露常驻工具并通过 `tool_search` 按需加载;`auto` 在可延迟工具数超过阈值后启用 | `auto` | | `discovery_min_tools` | `auto` 模式下触发工具发现的可延迟工具数量阈值 | `10` | | `discovery_search_limit` | 单次 `tool_search` 最多返回并加载的工具数 | `5` | -| `discovery_max_loaded_tools` | 一次 LLM 工具调用循环中最多动态加载的工具总数 | `12` | -| `always_loaded` | 工具发现开启时仍然常驻暴露的工具名列表;未配置时回退下表内置默认集 | `["tool_search", "tool_list", "get_identity", "list_memories", "search_web", "activate_skill"]` | +| `discovery_max_loaded_tools` | 新增工具时比较当前已加载总数,常驻工具计入;达到该值后停止新增,已有集合保留 | `12` | +| `always_loaded` | 工具发现开启时直接提供给模型的常驻工具名列表;未配置时回退下表内置默认集 | `["tool_search", "tool_list", "get_identity", "list_memories", "search_web", "activate_skill"]` | `tool_search` 和 `tool_list` 是本地元工具,不依赖 Claude 原生 tool search。接入大量 MCP 工具时,模型会先用 `tool_search` 搜索相关能力;搜索不到时可用 `tool_list` 列出工具组、工具名或按精确工具名加载工具,下一轮再调用被加载的真实工具。 @@ -214,9 +223,11 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 - 用 `enabled` 启用可选内置工具(如 `draw_svg`):MCP 工具不再被名单误过滤,通常无需改动。 - 需要严格工具白名单的部署:显式设置 `enabled_mode = "replace"`,恢复精确过滤语义。 -### `[[providers]]` — Provider 定义(可多个) +### `providers` — Provider 定义(可多个) + +`providers` 是有序渠道数组。协议、地址、身份和认证字段在渠道层维护;请求参数保存在渠道 `parameters` 中,模型 `parameters` 可覆盖全部同名请求参数。优先级为调用/会话覆盖 → 模型 → 渠道 → 具有覆盖职责的全局设置 → 内置默认;null 继承,空字符串、空对象、false 与合法零值均为显式值。`extra_body` 整体替换对象。 -每个 provider 一个 `[[providers]]` 条目: +渠道固定字段位于 `providers[]` 对象: | 键 | 说明 | 默认值 | |----|------|--------| @@ -225,31 +236,63 @@ GHCR 分发镜像和 `prod.example/Dockerfile` 均基于 Playwright Python 镜 | `base_url` | API 中转地址 | — | | `api_key_env` | API key 所在环境变量名 | — | | `default_model` | 默认模型 ID | — | -| `models` | 可用模型 ID 数组 | — | -| `enabled` | 暂时禁用该 provider:不进 `/llm providers`/`models` 列表、不参与探活、model_cascade 跳过、`/llm use` 拒绝;provider 保留在配置中,改回 `true` 即恢复 | `true` | +| `models` | 有序模型对象数组:`model`、`enabled`、`vision`、`context_window`、`parameters` | `[]` | +| `enabled` | 暂时禁用该 provider:不进 `/llm providers`/`models` 列表、不参与探活、级联候选跳过、`/llm use` 拒绝;provider 保留在配置中,改回 `true` 即恢复 | `true` | +| `aliases` | 有序别名对象数组,如 `[{"alias":"gpt4","model":"gpt-5.4"}]` | `[]` | +| `headers` | 注入到每次请求的额外 HTTP 头 | — | +| `client_fingerprint` | 渠道固定字段,选择请求指纹 Profile;见[请求指纹 Profile](client-fingerprints.md) | `default` | +| `fallback_urls` | 备用 base URL 列表,主地址 5xx/网络错误时自动切换 | `[]` | +| `proxy` | HTTP(S) 代理地址(如 `http://127.0.0.1:7890`),所有请求均走代理,含 fallback 重试 | — | +| `auth_method` | 认证方式:`api_key` 或 `bearer`。Claude 分别使用 `x-api-key` / `Authorization: Bearer`;Gemini 的 API Key 传输由 Profile 决定:新默认使用 `x-goog-api-key`,legacy-gemini 使用 `?key=`;显式 bearer 使用 `Authorization: Bearer`;OpenAI 使用 Bearer | `api_key` | +| `parameters` | 渠道请求参数对象;模型的同名对象可覆盖参数 | `{}` | + +模型对象位于 `models[]`:`model` 为实际请求模型名,`enabled` 控制可用性,`vision` 声明图片能力(缺省 true),`context_window` 声明容量,`parameters` 保存模型请求参数覆盖。别名对象与模型清单均在渠道层维护。 + +以下请求参数位于渠道或模型的 `parameters` 对象: + +| 键 | 说明 | 默认值 | +|----|------|--------| | `timeout_seconds` | 请求超时(秒) | `45` | | `temperature` | 温度参数 | `0.8` | | `max_output_tokens` | 最大输出 token 数 | `800` | | `style_overrides` | 可选,多行字符串,追加到每次调用的 system prompt 末尾 | — | -| `style_profile` | 可选,引用 `[style_profiles]` 中预定义的共享 system prompt 段,与 `style_overrides` 拼接 | — | -| `non_vision_models` | 该 provider 下不支持图片输入的模型 ID 列表 | `[]` | +| `style_profile` | 可选,引用 `style_profiles` 中预定义的共享 system prompt 段,与 `style_overrides` 拼接 | — | | `stream_enabled` | 是否启用 SSE 流式响应 | `true` | -| `aliases` | 模型短别名映射,如 `{ gpt4 = "gpt-5.4" }`,`/llm use` 时自动解析 | — | -| `headers` | 注入到每次请求的额外 HTTP 头 | — | | `user_agent` | 自定义 User-Agent 请求头 | — | -| `extra_body` | 注入到每次请求体的额外 JSON 字段(TOML inline table) | — | -| `fallback_urls` | 备用 base URL 列表,主地址 5xx/网络错误时自动切换 | `[]` | -| `proxy` | HTTP(S) 代理地址(如 `http://127.0.0.1:7890`),所有请求均走代理,含 fallback 重试 | — | -| `auth_method` | 认证方式:`api_key` 或 `bearer`。Claude 分别使用 `x-api-key` / `Authorization: Bearer`;Gemini 分别使用兼容中转的 `?key=` / `Authorization: Bearer`;OpenAI 使用 Bearer | `api_key` | +| `extra_body` | 注入到每次请求体的额外 JSON 对象,模型覆盖时整体替换 | — | | `prompt_caching` | 启用 Anthropic Prompt Caching(仅 `claude` 协议生效,需中转站支持 CLI 格式) | `false` | | `cache_ttl` | Claude prompt cache TTL:空值默认 5min,`"1h"` 使用扩展缓存(仅 `claude` 协议生效) | `""` | | `builtin_search` | 声明 provider 原生搜索工具(仅 `gemini` 协议生效):请求携带 `google_search` 服务端检索声明,回复末尾自动附上 grounding 来源;开启后该 provider 的会话移除 `search_web` 工具,提示词引导同步切换。其他协议下该键不生效(配置加载时记录 warning)。检索在 provider 侧执行并计费,本地轮次上限与 token 看板不覆盖 grounding 调用本身。注意:`google_search` 与 function calling 在同一请求中组合仅 Gemini 3 系列模型支持;2.x 模型需关闭该 provider 的 `builtin_search` 或全局 `tool_calling_enabled`,否则聊天请求会被 API 拒绝 | `false` | | `responses_profile` | `openai_responses` 协议专属:后端能力位。`openai-public`(官方 `/v1/responses`)或 `codex-http-relay`(Codex 形态中转,不发 `service_tier`、容忍 `codex.*` 结构事件、终态缺省字段时以流式完整 item 为回放基准) | `openai-public` | -| `reasoning_effort` | 思考档位(全协议生效):`low` / `medium` / `high` / `xhigh` / `max` / `ultra`,留空不发送思考参数(模型默认档)。到 wire 参数的翻译按协议与后端能力自动完成,超档自动钳制(详见下文「思考档位跨协议映射」)。群内可用 `/llm effort <档>` 按群覆盖本配置(`/llm effort default` 恢复跟随;`/llm status` 与 `/llm effort status` 展示配置档 / 覆盖档 / 生效档三层口径);Web Admin「配置」页提供本键的结构化编辑面板,「群 LLM」页提供按群三态覆盖 | `""` | - -> **会话纪元覆盖**:`[runtime]` 的 6 个 `epoch_*` 键可在本表同名覆盖(如 `epoch_cold_idle_seconds = 21600` 放宽 DeepSeek 的冷场判定),未覆盖的键继承全局缺省;详见 `[runtime]` 段说明。 - -> **预算与模型容量覆盖**:`request_input_token_budget`(显式请求输入预算,优先于窗口推导)与 `agent_replay_loop_tokens`(重放投影预算硬覆盖,优先于推导)可按 provider 覆盖。`model_context_windows` 以 inline table 声明 wire 模型名 → 上下文窗口 token 数(如 `{ "claude-sonnet-4-6" = 200000 }`);未显式配置的模型按内置策展表按家族前缀解析(claude 200k、gemini-2.5/3 1M、gpt-5 400k 等),均未命中按 capacity unknown 处理(只保证应用侧估算预算)。中继自定义模型名建议显式配置。**升级提示**:自本版本起,模型名命中内置窗口表的既有部署无需任何配置改动即可获得按窗口推导的更大请求/重放预算(例如 gemini-2.5 系列的重放预算从 4096 量级放大到数十万 token);希望维持旧收紧行为的部署应显式配置 `agent_replay_loop_tokens` / `request_input_token_budget`。 +| `reasoning_effort` | 思考档位(全协议生效):`low` / `medium` / `high` / `xhigh` / `max` / `ultra`,留空不发送思考参数(模型默认档)。到 wire 参数的翻译按协议与后端能力自动完成,超档自动钳制(详见下文“思考档位跨协议映射”)。群内可用 `/llm effort <档>` 按群覆盖本配置(`/llm effort default` 恢复跟随;`/llm status` 与 `/llm effort status` 展示配置档 / 覆盖档 / 生效档三层口径);Web Admin“LLM 配置”页提供渠道及模型的结构化参数编辑,“群 LLM”页提供按群三态覆盖 | `""` | + +> **会话纪元覆盖**:`settings.runtime` 的 6 个 `epoch_*` 键可在本表同名覆盖(如 `epoch_cold_idle_seconds = 21600` 放宽 DeepSeek 的冷场判定),未覆盖的键继承全局缺省;详见 `settings.runtime` 段说明。 + +> **预算与模型容量覆盖**:`request_input_token_budget` 与 `agent_replay_loop_tokens` 可在渠道或模型 `parameters` 中覆盖。每个模型的 `context_window` 声明上下文容量,null 时按现有模型家族注册表自动解析;未命中时标为容量未知。有效参数接口同时返回配置值、最终值及来源。 + + +#### 渠道与模型的 JSON 层级 + +以下为结构片段;编辑完整导出文件时更新对应渠道,保留其他渠道及被引用的模型。固定连接字段位于渠道对象,模型温度覆盖位于该模型的 `parameters`: + +```json +{ + "providers": [ + { + "id": "openai-main", + "protocol": "openai", + "base_url": "https://your-openai-relay.example/v1", + "api_key_env": "OPENAI_API_KEY", + "default_model": "gpt-5.4", + "client_fingerprint": "default", + "parameters": {"temperature": 0.8, "max_output_tokens": 2048}, + "models": [ + {"model": "gpt-5.4", "parameters": {"temperature": 0.5}} + ] + } + ] +} +``` #### 思考档位跨协议映射 @@ -268,70 +311,86 @@ openai chat 渠道再按模型家族钳制:DeepSeek / GLM-5.3 / Kimi k3 三档 #### 各协议思考参数支持面 -归一化层只做映射、不做门禁:除 claude budget 的 1024 数学下限外,任何 协议×模型 组合都按上表形态照发思考参数。后端不支持某档位时上游返回 400 等 4xx 错误,即为不支持的信号。群内用 `/llm effort default` 清除会话覆盖并恢复跟随 provider 配置;provider 配置档也不受支持时,清除该 provider 的 `reasoning_effort` 并执行 `/llm reload`,让模型使用自身默认档。以下为 2026-10 调研的各后端支持面结论,供排障参考(不构成拦截依据): +QuickQuip 按上表转换思考档位,Claude 旧式预算须满足 1024 的下限;其他组合会把转换后的参数提交给后端。后端不支持某档位时上游返回 400 等 4xx 错误,即为不支持的信号。群内用 `/llm effort default` 清除会话覆盖并恢复跟随 provider 配置;provider 配置档也不受支持时,清除该 provider 的 `reasoning_effort` ,保存草稿并应用,让模型使用自身默认档。以下为 2026-10 调研的各后端支持面结论,供排障参考: - **claude 协议**:Anthropic 官方 extended thinking 自 3.7 代际引入,3.5 及更早代际无思考能力(照发旧式 budget 形态会被上游拒绝);4.7+/5 系只接受 `adaptive + effort`,旧式 `type:"enabled"` 直接 400。 -- **claude 兼容端点**:Kimi(k3,思考词表 low/high/max)、MiMo(m2.5)、GLM Coding Plan 等均提供面向 Claude Code 类客户端的 Anthropic 形态端点,对 `adaptive + effort` 形态有实测旁证(端点把 Claude Code 的 effort 标签映射到自家思考档位)。照发意味着同时适用 Anthropic 温度契约:档位生效期间非默认 `temperature` 不下发,上游若实际忽略思考参数,温度配置也会随之不生效。个别端点对历史回放中的 thinking 内容块有特殊要求,QuickQuip 的历史回放分三层:无工具调用的对话轮与一切降级路径(owner 失配、敏感命中、预算精简、档案投影)为纯文本,不带 thinking;带工具调用的已关闭 Loop 在 owner 五元组匹配且结构有效时走原生回放,跨轮携带 thinking 内容(claude 签名块 / gemini thought parts / responses reasoning 密文)。对 claude 兼容端点的实际避坑机制是签名有效性闸门:无签名的 thinking 块自动降级为纯文本档案。注意 claude 协议暂无 openai_responses 式的「上游 400 时剥历史推理降级重试」兜底,返回签名块却拒绝历史 thinking 的端点存在暴露面。 +- **claude 兼容端点**:Kimi(k3,思考词表 low/high/max)、MiMo(m2.5)、GLM Coding Plan 等均提供面向 Claude Code 类客户端的 Anthropic 形态端点,对 `adaptive + effort` 形态有实测旁证(端点把 Claude Code 的 effort 标签映射到自家思考档位)。照发意味着同时适用 Anthropic 温度契约:档位生效期间非默认 `temperature` 不下发,上游若实际忽略思考参数,温度配置也会随之不生效。个别端点对历史回放中的 thinking 内容块有特殊要求,QuickQuip 的历史回放分三层:无工具调用的对话轮与一切降级路径(owner 失配、敏感命中、预算精简、档案投影)为纯文本,不带 thinking;带工具调用的已关闭 Loop 在 owner 五元组匹配且结构有效时走原生回放,跨轮携带 thinking 内容(claude 签名块 / gemini thought parts / responses reasoning 密文)。对 claude 兼容端点的实际避坑机制是签名有效性闸门:无签名的 thinking 块自动降级为纯文本档案。注意 claude 协议暂无 openai_responses 式的“上游 400 时剥历史推理降级重试”兜底,返回签名块却拒绝历史 thinking 的端点存在暴露面。 - **gemini 协议**:`thinkingLevel` 为 3.x 形态;2.x 只认 `thinkingBudget`,其中 2.0 系无思考参数支持(照发会被上游拒绝);level 与 budget 不可同传。 - **openai chat**:`reasoning_effort` 词表按家族见上表;词表外档位被上游忽略或拒绝,表现以后端为准。 - **openai_responses**:profile 词表即支持面(降档口径见上节)。 -> **内联媒体预算**:`max_inline_media_bytes`(provider 级键,缺省 `5242880`,`0` = 不限)限制单次请求全部内联图片的解码字节总量,用户消息与各批工具结果共享预算和内容去重。发送前 GIF 自动取首帧转静态 PNG。超出单图上限或请求额度的图片先自动降采样重编码为 JPEG 压入剩余额度(原始尺寸优先、长边阶梯递减;额度低于 96KB 时不再压缩);压缩后仍装不下的第一张图片及其后低优先级图片全部跳过并记录日志。优先保留最新用户消息中的图片(当前 → 引用 → 近期),再按新到旧处理工具结果与历史用户图片。每次请求组装独立计算预算,协议中的消息与工具结果顺序保持完整。该预算同时决定请求体上限(图片 base64 膨胀约 4/3,缺省值对应最坏约 6.7MiB 请求体);上游网关按更紧的请求体或 token 口径风控(部分网关把图片 base64 按文本估算 token)的部署应显式配置更小的值。 +> **内联媒体预算**:`max_inline_media_bytes`(渠道或模型 `parameters` 中的键,缺省 `5242880`,`0` 表示不限)限制单次请求全部内联图片的解码字节总量,用户消息与各批工具结果共享预算和内容去重。发送前 GIF 自动取首帧转静态 PNG。超出单图上限或请求额度的图片先自动降采样重编码为 JPEG 压入剩余额度(原始尺寸优先、长边阶梯递减;额度低于 96KB 时不再压缩);压缩后仍装不下的第一张图片及其后低优先级图片全部跳过并记录日志。优先保留最新用户消息中的图片(当前 → 引用 → 近期),再按新到旧处理工具结果与历史用户图片。每次请求组装独立计算预算,协议中的消息与工具结果顺序保持完整。该预算同时决定请求体上限(图片 base64 膨胀约 4/3,缺省值对应最坏约 6.7MiB 请求体);上游网关按更紧的请求体或 token 口径风控(部分网关把图片 base64 按文本估算 token)的部署应显式配置更小的值。 -> **协议适配说明**:`claude` 协议的请求默认带上完整的 Claude Code 客户端指纹头(`anthropic-version`、`anthropic-beta`、`x-app: cli`、全套 `x-stainless-*` 运行时遥测头、`anthropic-dangerous-direct-browser-access` 等),User-Agent 与 URL(`/messages?beta=true`)均对齐真实 claude-cli 客户端。`x-stainless-os` 按宿主 OS 动态探测。所有指纹头均可通过 `headers` 配置大小写无关地覆盖,`user_agent` 配置项优先级最高。 +> **协议适配说明**:内置 `default` 的 Chat、Responses、Anthropic 和 Gemini 分别使用 OpenCode 2、轻量 Codex、Claude Code 和 Gemini CLI 策略;`legacy-claude` 与 `legacy-gemini` 提供通用行为回退。Profile 的版本、UA、Header 和 Body 特征由随包 TOML 定义,本地同名模板整份覆盖,修改后重启 Bot 与 Web。附加头按大小写无关覆盖,`user_agent` 参数优先级最高;自动 Session 与 Body 结构字段受到覆盖保护。`quickquip` Profile 使用自有品牌,四个专有头由该 Profile 管理;全局 `settings.runtime.client_cluster` 可配置部署群标识,`settings.runtime.claude_code_device_id` 可填写 CC 设备身份。详情见[请求指纹 Profile](client-fingerprints.md)。 > **Gemini 工具回放说明**:`gemini` 协议会把模型返回的有序 `parts` 作为 provider opaque data 保留,并在工具结果回送时原样恢复 `thoughtSignature`。并行 `functionCall` 与 `functionResponse` 必须保持完整批次;超过单轮工具上限时本轮 fail-closed,不向 Gemini 发送截断历史。工具结果图片放在完整 `functionResponse` 批次之后的独立 user turn。连接只接受 Bearer token 的原生 Gemini 网关时设置 `auth_method = "bearer"`,避免凭据进入 URL 和代理访问日志。 > **Responses 协议说明**(1.16 起):`openai_responses` 协议采用 `store:false` 手动上下文管理,每轮全量回放 input items;reasoning 模型的当前工具循环会把 reasoning 密文与原生 output items(保序)原样回传,保证官方端点的连续工具调用可续接;工具批次超出单轮执行限额时整批拒绝(与 Gemini 同款 fail-closed)。跨轮 reasoning 密文回放已启用:owner 身份维度为 provider / 协议 / 模型 / profile / 端点(不含档位),同一 owner 的会话保留完整推理连续性,历史工具循环按原生形态回放;切换档位时新旧密文混档共存、回放估算按各密文生成档位逐条计量,切换其余维度自动降级为通用投影(工具事实保留),历史损坏或预算不足时按精简阶梯处理,上游拒绝历史形状时自动去除历史推理重试一次。部分思考系模型只接受默认温度,如遇请求被拒可把该 provider 的 `temperature` 调回 `1.0`。 -### `[style_profiles]` — 共享风格段 +### `style_profiles` — 共享风格段 定义可被多个 provider 复用的 system prompt 风格段(多行字符串),provider 通过 `style_profile` 键引用: -```toml -[style_profiles] -my_family = """ -……风格条目…… -""" - -[[providers]] -id = "my-provider" -style_profile = "my_family" # 引用共享段 -style_overrides = "……" # 可选,叠加微调 +```json +{ + "style_profiles": { + "my_family": "……风格条目……\n" + }, + "providers": [ + { + "id": "my-provider", + "parameters": { + "style_profile": "my_family", + "style_overrides": "……" + } + } + ] +} ``` - 拼接顺序:`style_profile` 段在前,`style_overrides` 追加在后,整体附加到每次调用的 system prompt 末尾。 - 家族内容为空串是合法形态:声明家族占位、不注入任何内容,引用方等价于无风格附加块(例如为后续调校预留条目)。 -- 引用未定义的 `style_profile` 会记录 error 日志并忽略该引用,provider 仅保留 `style_overrides`。 -- 内置示例四家族(`openai_family` / `gemini_family` / `claude_family` / `general`)随 `config/llm.toml.example` 分发,按模型谱系对位命名,可直接复用或改写。 - -### `[pricing.models]` — 模型定价(成本统计) - -per-MTok(每百万 token,USD)定价表,是 Web Admin LLM 用量页成本统计(`cost_usd`)的价格来源: - -```toml -# 纯 model 名 = 官方价默认(所有 provider 的该 model 共享) -[pricing.models."deepseek-chat"] -input_per_mtok = 0.14 -output_per_mtok = 0.28 -cache_read_per_mtok = 0.003 - -# "provider_id/model" = per-provider 覆盖(某中转实际价,优先于 model 默认) -[pricing.models."my-provider/deepseek-chat"] -input_per_mtok = 0.20 -output_per_mtok = 0.40 +- 引用未定义的 `style_profile` 会导致保存失败并返回依赖冲突;删除被引用的风格前应处理引用。 +- 内置示例四家族(`openai_family` / `gemini_family` / `claude_family` / `general`)随 `config/llm.json.example` 分发,按模型谱系对位命名,可直接复用或改写。 + +### `pricing` — 模型定价(成本统计) + +per-MTok(每百万 token,USD)定价表,是 Web Admin LLM 用量页成本统计(`cost_usd`)的价格来源。下例使用示意单价,假设 `deepseek` 与 `my-provider` 是两个已配置渠道,均提供 `deepseek-chat`;请按实际渠道 ID 和请求模型名修改完整导出文件的 `pricing` 数组: + +```json +{ + "pricing": [ + { + "provider_id": "deepseek", + "model": "deepseek-chat", + "input_per_mtok": 0.14, + "output_per_mtok": 0.28, + "cache_read_per_mtok": 0.003 + }, + { + "provider_id": "my-provider", + "model": "deepseek-chat", + "input_per_mtok": 0.2, + "output_per_mtok": 0.4 + } + ] +} ``` | 键 | 说明 | |----|------| +| `provider_id` | 已存在的渠道 ID,须为非空字符串 | +| `model` | 该渠道实际请求发出的模型 ID,同名模型在各渠道单独定价 | | `input_per_mtok` | 输入价(USD/百万 token) | | `output_per_mtok` | 输出价(USD/百万 token) | | `cache_read_per_mtok` | 缓存读价;模型无该缓存机制时省略,计算时回退 input 价 | | `cache_write_per_mtok` | 缓存写价;同上,无缓存溢价时省略 | -查价顺序:先查 `"provider_id/model"`(per-provider 覆盖),未命中回退纯 `"model"`(官方价默认),再未命中标记未定价(cost=0,用量页显示“未定价”)。第三方中转建议按模型 id 填官方价默认,再按中转实际计费加 provider 覆盖;国产 CNY 价按汇率换算成 USD。 +价格按 `(provider_id, model)` 唯一匹配,模型名取本次请求实际发送的值;上游响应中改写的模型名不影响定价。免费模型填写零价格;缺少匹配记录时标记“未定价”,成本暂计为 0。同名模型在不同渠道分别配置,第三方渠道按实际计费填写 USD 单价。缓存价格为 null 或省略时回退输入价。 -### `[skills]` — Skill 系统 +JSON 交换格式当前为整数 `format_version: 2`。导入旧格式 1 时,后端将可明确归属的全渠道价格转换到渠道,并在预览中列出归属和移除结果;导入前核对报告,保存草稿后单独应用。升级与导入规则见 [LLM 配置管理](llm-configuration.md#模型参数与引用)。 + +### `settings.skills` — Skill 系统 | 键 | 说明 | 默认值 | |----|------|--------| @@ -344,15 +403,15 @@ output_per_mtok = 0.40 | `script_timeout_ms` | `run_skill_script` 默认超时(毫秒);单次调用可另行指定,硬上限 120000 | `30000` | | `script_max_output_bytes` | 脚本 stdout/stderr 各自的输出字节上限,超限截断 | `65536` | -非法取值回退默认值并记录告警。`skills/` 为空目录或不存在时工具不注册、系统提示不变。部署方式、目录约定与安全模型见 [skills.md](skills.md)。 +新写入的非法取值由字段校验拒绝,并返回字段路径及约束。`skills/` 为空目录或不存在时工具不注册、系统提示不变。部署方式、目录约定与安全模型见 [skills.md](skills.md)。 -### `[mcp]` — MCP 总开关 +### `settings.mcp` — MCP 总开关 | 键 | 说明 | |----|------| | `enabled` | 是否启用 MCP | -### `[[mcp.servers]]` — MCP Server 定义(可多个) +### `settings.mcp.servers` — MCP Server 定义(可多个) | 键 | 说明 | |----|------| @@ -383,7 +442,7 @@ output_per_mtok = 0.40 `include_tools` 为空时默认接入该 MCP server 暴露的全部工具;`exclude_tools` 会在白名单之后生效。接入 GitHub MCP 这类大工具集时,生产环境建议优先使用 `include_tools` 收窄到读类工具,再交给 `tool_search` / `tool_list` 做按需加载。 -### `[daily_briefing]` — 每日播报 +### `settings.daily_briefing` — 每日播报 | 键 | 说明 | |----|------| @@ -397,11 +456,11 @@ output_per_mtok = 0.40 | `sample_messages_limit` | 消息样本数量上限 | | `max_context_chars` | 送给模型的上下文字符上限 | | `max_output_chars` | 最大输出字符数 | -| `model_cascade` | 模型级联列表(provider + model,失败自动降级) | +| `routes.daily_briefing` | 独立的模型级联候选列表(失败自动降级) | -`model_cascade` 会按顺序尝试;如果某个模型提前截断或以非正常 finish reason 结束,会继续尝试下一项(不完整的正文一律不放行)。聊天记录容量与输出上限按**每跳模型自己的上下文窗口**逐跳推导(容量未知回退保守缺省);输出上限缺省请求:日报 16384、简报与周/月报 8192,输出配额低于该值的模型会在该跳直接报错——级联模型需能接受相应输出上限。仅当对应功能 `enabled = true` 时才校验 cascade 引用的 provider 是否存在;功能关闭时跳过校验,不产生 `load_error`。 +`routes.<报告类型>` 候选会按顺序尝试;如果某个模型提前截断或以非正常 finish reason 结束,会继续尝试下一项(不完整的正文一律不放行)。聊天记录容量与输出上限按**每跳模型自己的上下文窗口**逐跳推导(容量未知回退保守缺省);输出上限缺省请求:日报 16384、简报与周/月报 8192,输出配额低于该值的模型会在该跳直接报错——级联模型需能接受相应输出上限。所有级联引用在保存时均检查存在性,包括关闭的功能。JSON 候选使用 `{"provider_id":"渠道","model":"wire 模型名"}`,`@default` 保持运行时动态解析。 -### `[daily_summary]` — 每日总结 +### `settings.daily_summary` — 每日总结 | 键 | 说明 | |----|------| @@ -410,11 +469,13 @@ output_per_mtok = 0.40 | `publish_cron` | 发布 cron 表达式 | | `min_messages` | 最小消息数(不足时跳过) | | `summary_length_hint` | 目标字数 | -| `model_cascade` | 模型级联列表(失败自动降级) | +| `routes.daily_summary` | 独立的有序模型候选列表(失败自动降级) | + +### `settings.weekly_report` / `settings.monthly_report` — 群周报 / 群月报 -### `[weekly_report]` / `[monthly_report]` — 群周报 / 群月报 +每周一(周报)/每月 1 日(月报)自动生成上一周期的群聊回顾。数据源为聊天记录归档 `data/chat_archive.db`,持续采集全部群的消息,应用不自动清理历史。 -每周一(周报)/每月 1 日(月报)自动生成上一周期的群聊回顾。数据源为聊天记录归档(`chat_archive.db`,全群 always-on、永不删除)。周报把全量消息经压缩序列化(按天分节、同分钟连发合并、复读折叠、URL 只留域名)后一次成文;月报按周公平分配字符预算组装输入,周内高活跃日优先整日保留,低活跃日抽稀,终稿输入维持在目标量级(默认约 24 万字符)。与 `[daily_summary]` 相互独立,可单独开启。 +周报将全周期记录按天分节,合并同分钟连发、折叠复读并缩短 URL,再交给模型生成。月报按自然周分配字符预算:周内先完整保留记录较少的平静日,再按消息数量从多到少处理高活跃日;能放入剩余额度时完整保留,放不下时等距抽样,额度耗尽时跳过剩余日期。默认终稿输入预算约 24 万字符。周报、月报与 `settings.daily_summary` 相互独立,可单独开启。 | 键 | 说明 | |----|------| @@ -423,8 +484,8 @@ output_per_mtok = 0.40 | `publish_cron` | 发布 cron(默认 `0 10 * * *` 每天 10:00;周报/月报共用,每日发布新报告并补发未发布的) | | `min_messages` | 周期内最小消息数(不足时跳过;周报默认 100,月报默认 300) | | `length_hint` | 目标字数(周报默认 2000,月报默认 2500) | -| `input_char_budget` | 月报终稿聊天记录字符预算(默认 240000,最小 8000;分周公平分配,周内活跃日优先) | -| `model_cascade` | 模型级联列表,支持 `@default` 占位符 | +| `input_char_budget` | 月报终稿聊天记录字符预算(默认 240000,最小 8000;按自然周分配,周内先保留平静日,再处理高活跃日) | +| `routes.weekly_report` / `routes.monthly_report` | 独立的有序模型候选列表,支持 `@default` | > 周报/月报通过 `/summary weekly|monthly on|off|status|now` 在群内按群开启。period 标识:周报为 ISO 周号(如 `2026-W24`),月报为年月(如 `2026-06`)。 @@ -432,7 +493,7 @@ output_per_mtok = 0.40 ## config/generation.toml -此文件不存在时,各模态段回退读取 `config/llm.toml` 中的旧版配置段:图片 `[image_generation]`、语音 `[audio_generation]`、音乐 `[music_generation]`、语音识别 `[asr]`、SVG `[svg]`。 +此文件负责图片、语音、音乐、ASR 和 SVG 的独立配置。首次迁移会将旧 TOML 中的内联多模态段提取到这里;已存在的独立文件保留其权威性。文件缺失时各能力使用关闭状态的默认配置。 图片、语音和音乐的 `prompt_blocklist` 是生成业务专属限制。配置了`config/sensitive_words.toml` 时,生成 prompt、标题、歌词和引用文本还会经过部署级统一敏感词过滤。该检查只处理文本,不审核输入或输出的图片像素、音频波形和音乐成品。 @@ -539,13 +600,13 @@ ASR 用于把 OneBot V11 `record` 语音消息转写为文字,并注入 LLM ### `[svg]` — SVG 画图(`draw_svg` 工具) -LLM 对话中自主调用 `draw_svg` 工具:模型在工具参数中直接写出 SVG 源码,本地 resvg 渲染成 PNG 随回复外发。不需要配置 provider/model(SVG 代码由当前群的对话模型生成),渲染字体沿用 `data/fonts/NotoSansSC-Regular.ttf`(与词云同源)。启用需两步:本段 `enabled = true`,且 `llm.toml [tools] enabled` 中加入 `"draw_svg"`。 +LLM 对话中自主调用 `draw_svg` 工具:模型在工具参数中直接写出 SVG 源码,本地 resvg 渲染成 PNG 随回复外发。不需要配置 provider/model(SVG 代码由当前群的对话模型生成),渲染字体沿用 `data/fonts/NotoSansSC-Regular.ttf`(与词云同源)。启用需两步:本段 `enabled = true`,且 `settings.tools.enabled` 中加入 `"draw_svg"`。 | 键 | 默认 | 说明 | |----|------|------| | `enabled` | `false` | 功能总开关 | | `harden` | `true` | 第一层安全(默认启用):输入硬约束(64KB / 嵌套 ≤2000 / viewBox ≤2048 / 滤镜参数上限)、静态清洗(剥 script、事件属性、外链)、输出尺寸服务端覆盖、渲染子进程 rlimit(地址空间 2GB / CPU 5s)。关闭即自担风险:恶意 SVG 可耗尽内存或 CPU | -| `content_judge` | `false` | 第二层安全(默认关闭):渲染前用 `[triggers.quick_judge]` 的廉价模型对图片可见文本做内容安全裁决。判定失败(超时 / 非 JSON / 未配置)时放行渲染并记录 WARN(fail-open) | +| `content_judge` | `false` | 第二层安全(默认关闭):渲染前用 `settings.quick_judge` 的廉价模型对图片可见文本做内容安全裁决。判定失败(超时 / 非 JSON / 未配置)时放行渲染并记录 WARN(fail-open) | 渲染始终在独立子进程内执行(结构性防段错误,不受 `harden` 开关影响);渲染限流为全局 10 次/分钟、单用户 2 次/分钟,单次回复最多外发 3 张图片。 @@ -707,15 +768,15 @@ trigger_pattern = '^开始(.+?)接龙$' chain = ['第一', '第二', '第三'] ``` -`ChainGameManager` 通用引擎支持捕获组和 OR 候选匹配。 +接龙支持正则捕获组,以及一个步骤接受多个候选词的匹配方式。 ### 自动回复概率 所有限流桶和规则都可配置 `probability`(取值 `[0, 1]`,缺省 `1` 表示行为不变)。命中自动回复后先掷骰再执行:未掷中则本次保持沉默,不消耗限流桶配额,也不花费语境规则的 LLM 判定成本。 -适用范围为全部非命令触发的自动回复:文字规则、语境规则、时区回复、被动「xxx了」判定、复读检测、乖女链、接龙、内置游戏、唤醒,以及显式 LLM 回复(@ / 前缀 / 私聊)。斜杠命令(如 `/turmfluch`)不受影响。 +适用范围为全部非命令触发的自动回复:文字规则、语境规则、时区回复、被动“xxx了”判定、复读检测、乖女链、接龙、内置游戏、唤醒,以及显式 LLM 回复(@ / 前缀 / 私聊)。斜杠命令(如 `/turmfluch`)不受影响。 -取值顺序:规则级 `probability` > 引用桶的 `probability` > `1`。文字规则未掷中时只是该规则本次沉默,低优先级规则仍可竞争;被动「xxx了」和 `llm_context` 语境规则的掷骰发生在 LLM 判定之前。 +取值顺序:规则级 `probability` > 引用桶的 `probability` > `1`。文字规则未掷中时只是该规则本次沉默,低优先级规则仍可竞争;被动“xxx了”和 `llm_context` 语境规则的掷骰发生在 LLM 判定之前。 与限流的分工:概率控制平均密度("十条命中回五条"),限流桶兜底峰值上限("每分钟最多 N 条")。独立随机意味着会出现连续回复和长时间沉默的波动,属预期行为。 @@ -726,9 +787,13 @@ chain = ['第一', '第二', '第三'] 两个开关的计数状态按(规则, 群)隔离(私聊按用户隔离),只存内存、重启即重置;规则级 `probability` 只覆盖基础概率,两个开关始终跟随所在桶。`probability = 1` 搭配 `suppress_after_hit` 会出现"一回一哑"的规律交替,建议与 `probability < 1` 组合使用以保留随机感。 -三点语义边界:**"命中"指掷骰通过**,而非回复最终发出——掷骰之后语境判定未通过或限流拒绝时状态不回滚(防连发窗口可能消耗在未发出的回复上,方向保守、密度略低于配置值,语境规则的连哑按快筛命中计);**状态机类桶不建议配概率**——复读、接龙、内置游戏的进度与得分在匹配阶段即已推进,概率只静默丢弃回复(可能"赢了游戏无反馈但分数已记");状态表规模有上限(8192 个规则×群组合),超限整体重置,超大部署下防连发/保底可能周期性失效。 +调整防连发与保底时,请同时考虑: + +- 计数中的“命中”指掷骰通过。后续语境判定或限流拒绝时保留计数,防连发窗口可能消耗在未发出的回复上;语境规则的连哑按快筛命中计算。 +- 复读、接龙和内置游戏先推进进度与得分,再决定是否发送回复。为这些桶设置概率可能造成已记分却未发送结果,建议保留全量回复。 +- 计数最多保存 8192 个规则与群组合,超限会整体重置;大规模部署应考虑重置对防连发与保底的影响。 -两点注意:覆写系统预定义桶(如 `timezone_wake`、`sts_card_le`)时整个条目需重写,`global_limit` / `user_limit` 一并写全——只写 `probability` 的残缺条目在配置加载时会打警告,且会让限流器构建失败(启动即崩溃),这是既有整条替换语义;`llm_chat` 与唤醒类桶的概率调低后,bot 对直接 @ 也会偶发沉默,仅建议在确实需要全局静音降噪时使用。 +覆写系统预定义桶(如 `timezone_wake`、`sts_card_le`)时整个条目需重写,`global_limit` / `user_limit` 一并写全——只写 `probability` 的残缺条目在配置加载时会打警告,且会让限流器构建失败(启动即崩溃),这是既有整条替换语义;`llm_chat` 与唤醒类桶的概率调低后,bot 对直接 @ 也会偶发沉默,仅建议在确实需要全局静音降噪时使用。 随仓库分发的 `config/chat_rules.toml.example` 已按推荐密度预置各桶概率(含系统内置桶的覆写条目),并按触发词日常频率为新三国系列逐条分层配置规则级概率;未配置的规则行为与历史版本一致。 @@ -808,7 +873,7 @@ server: 默认暴露在 `http://127.0.0.1:8888`,开启 JSON 接口供 bot 直接调用。 -> ⚠️ **搜索质量免责**:`search_web` 只负责把请求转发给 SearXNG 实例,结果相关性取决于该实例聚合的搜索引擎与出口 IP(机房 IP 下 bing/baidu/sogou 等常大面积失效或风控)。QuickQuip 不为搜索结果质量背书;生产环境建议使用调优过的或自建 SearXNG 实例。 +搜索结果由所配置的 SearXNG 实例聚合。结果为空或相关性差时,先检查该实例的搜索引擎状态、网络出口与查询设置,再用同一关键词复测。 --- diff --git a/skills.example/self-docs/references/docs-admin-deployment.md b/skills.example/self-docs/references/docs-admin-deployment.md index 47f2b91c..824871b7 100644 --- a/skills.example/self-docs/references/docs-admin-deployment.md +++ b/skills.example/self-docs/references/docs-admin-deployment.md @@ -2,44 +2,22 @@ # QuickQuip 云端部署指南 -LLM 模块的详细结构、边界和群内命令说明见 [docs/dev/llm-module.md](../../docs/dev/llm-module.md)。如果后续需要把外部工具后端接成 MCP,另见 [docs/dev/mcp-integration.md](../../docs/dev/mcp-integration.md)。 +本文面向 Linux 服务器部署者,介绍自动发布与手动安装、首次登录及日常维护。LLM 配置与升级见[配置管理](llm-configuration.md),外部工具接入见[MCP 接入指南](mcp-servers.md)。 ## 前提条件 -- 一台 Linux 服务器(建议 2 核 / 2G 内存并配置 2G swap:v1.12.2 验收实测此规格运行全栈稳态约 0.7G RSS、无 OOM;1 核 1G 无 swap 的极端规格未经全栈验证,不建议) +- 一台 Linux 服务器,建议从 2 核/2G 内存及 2G swap 起步。v1.12.2 全栈验收在此规格下稳态约 0.7G RSS,后续按并发量与启用功能观察内存 - 已安装 Docker 和 Docker Compose - Node.js + pnpm(手动部署构建前端用;deploy 脚本路径本机需有) - QQ 账号(用于 OneBot 协议端登录;当前部署模板默认适配器为 LLBot,各适配器状态见 [onebot-adapters.md](onebot-adapters.md)) -## 推荐服务器 +## 选择服务器 -| 方案 | 价格 | 优缺点 | -|------|------|--------| -| Oracle Cloud Free Tier | 免费 | ARM 1 核 1G 永久免费,注册看运气,IP 可能被风控 | -| 腾讯云/阿里云轻量 | 50-100 元/年 | 国内网络延迟低,稳定,大促时性价比高 | -| 雨云/狗云等小厂 | 30-60 元/年 | 更便宜,稳定性看运气 | +按所需架构、资源容量及网络条件选择服务器。部署主机需要访问镜像源、QQ 协议端、所用模型 API 和搜索服务;先确认这些连接可用,再准备完整部署。费用按服务商当前报价核对。 ## 部署步骤 -### 自动部署与版本回滚 - -日常部署推荐使用 `prod/deploy-v4.sh`(Bash)或 `prod/deploy-v4.ps1`(PowerShell)。两端共享远端事务执行器,按清单上传应用文件,预检和构建成功后切换版本目录,并验证容器、OneBot 连接与 Web Admin HTTP。完整前提、参数与目录结构见 [生产模板说明](../../prod.example/README.md)。服务器需具备 rsync、flock、Python ≥ 3.11.8 和 Docker Compose ≥ 2.27。 - -```bash -# 在本地项目目录执行,使用自己的 SSH alias -bash prod/deploy-v4.sh --dry-run -bash prod/deploy-v4.sh --host-alias quickquip-prod -bash prod/deploy-v4.sh --status --host-alias quickquip-prod -bash prod/deploy-v4.sh --rollback --host-alias quickquip-prod -``` - -已有平铺部署首次使用 `--migrate`:先保存服务器上的旧代码与运行镜像作为基线,再部署候选版本。新服务器首次尚未扫码时可显式使用 `--skip-health`,之后完成扫码与健康核验。PowerShell 使用同名参数,指定回滚版本时使用 `-Rollback -ReleaseId `;完整接口见 `bash prod/deploy-v4.sh --help`。 - -每次发布携带一个版本标识:部署的 `pyproject.toml` 版本加上服务器镜像构建完成时刻(如 `1.15.3-dev.2+build.20260909.065235`),出现在事务日志的 `image built` 与 `release complete` 行中,便于将线上 release 目录与代码版本对上号。 - -部署失败时自动恢复本次修改的共享文件并验证旧版本健康;手动回滚保留当前根 `.env`、数据库与部署根共享 `skills/`(Skill 安装成果不随代码版本回退;目标 compose 使用共享目录时,回滚把目标 release 中新增的 Skill 非破坏性合并进共享目录)。回滚到挂载 release 自带 `skills/` 副本的历史版本时,该版本继续使用其目录内副本。数据迁移与外部副作用不随代码回滚,部署前应核对版本升级说明。`--dry-run` 会本地构建前端,PowerShell 还会临时打包,两者均不连接远端。 - -运行态位于部署根目录的 `data/`、`skills/` 和 `prod/`,版本内容位于 `releases//`,`current` 与 `previous` 指向当前和前一版本。运维命令需按 [模板中的手动访问步骤](../../prod.example/README.md#manual-compose-access) 导出部署根目录和版本标识。下列步骤说明手动平铺安装;版本目录部署由上述脚本管理。 +首次手动安装按下方 1–7 步进行,先准备配置并启动服务,再登录后台启用 LLM。选择部署脚本时,先在本地准备第 3 步所列的模板、配置与资产,并按[生产模板的 SSH 设置](../../prod.example/README.md#ssh-connection)配置服务器连接;搜索服务需先在服务器上准备。准备完成后使用本文后面的[自动部署与版本回滚](#自动部署与版本回滚)。 ### 1. 服务器上安装 Docker @@ -64,15 +42,35 @@ scp -r /path/to/QuickQuip user@server:/path/to/QuickQuip ```bash cd /path/to/QuickQuip cp .env.example .env -nano .env # 填入 QQ 号、OneBot 配置和 API key +nano .env # 按下表填写;QUICKQUIP_SEARXNG_BASE_URL 需取消注释 cp -r prod.example prod # prod/ 已存在时会嵌套成 prod/prod.example(部署脚本会中止并提示),详见 prod.example/README.md ``` -同时确认: +启动前在根 `.env` 填写: + +| 配置 | 填写方法 | +|---|---| +| `QQ_ACCOUNT` | 用于机器人登录的 QQ 号 | +| `ONEBOT_ACCESS_TOKEN` | 自己设置的连接口令;第 5 步在 LLBot 填入同一值 | +| `DRIVER` | 正向 WebSocket 使用示例值 `~fastapi+~websockets` | +| `WEB_ADMIN_PASSWORD` | 自己设置的后台登录口令,替换示例占位值 | +| `QUICKQUIP_SEARXNG_BASE_URL` | 从 Bot 容器可访问的 SearXNG 地址,生产模板启动时必填 | +| 所选渠道的 API key | 启用 LLM 时填写该渠道使用的变量,例如 `OPENAI_API_KEY` | + +已有 SearXNG 时填写它的地址。首次自建可以在**服务器项目根目录**用仓库根模板单独启动搜索服务:在 `.env` 中把 `SEARXNG_SECRET` 改成自己的随机字符串,保留示例的 `SEARXNG_BIND_PORT=8888`,执行下面的 Bash 命令。 + +```bash +SEARXNG_BIND_ADDRESS="$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Gateway}}')" \ + docker compose -p quickquip-search -f docker-compose.example.yml up -d searxng +docker network inspect bridge --format 'QUICKQUIP_SEARXNG_BASE_URL=http://{{(index .IPAM.Config 0).Gateway}}:8888' +``` + +将最后一行输出写入根 `.env`,替换注释中的 `QUICKQUIP_SEARXNG_BASE_URL`。这条 Linux Docker 路线将搜索端口绑定在默认 Docker 网桥地址,供 Bot 容器访问;[Docker 的端口映射说明](https://docs.docker.com/engine/network/port-publishing/)解释了跨网桥访问已发布端口的方式。`quickquip-search` 使用独立 Compose 项目名,后续应用发布会保留这个搜索服务。已有搜索服务的部署直接填写其可达地址即可。 + +LLM 初次使用将 `config/personas.example/` 复制为 `config/personas/`;服务启动后按第 7 步添加渠道并应用配置。其他功能按需准备: -- 根目录下的 `.env` 已存在,并填入 `OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`GEMINI_API_KEY` - 如启用 MCP sidecar,再按你的私有 `prod/` 编排补充对应 API key -- 根目录下的 `config/llm.toml` 已存在并填入真实 provider / model / base_url 配置 +- 已有部署升级时按[配置管理](llm-configuration.md#初始化与升级)完成数据库迁移,保留迁移前配置与资产备份 - 如启用图片、语音、音乐或 ASR,`config/generation.toml` 已存在并填入对应 provider 与模型 - 如启用低频唤醒,`config/awakening.toml` 已存在并填入阈值、兴趣话题和按群覆盖 - 如启用敏感词过滤,`config/sensitive_words.toml` 已存在并填入部署侧词表 @@ -80,10 +78,10 @@ cp -r prod.example prod # prod/ 已存在时会嵌套成 prod/prod.example( - `prod/` 已由 `prod.example/` 复制而来,并按服务器环境调整 compose、部署脚本或巡检脚本 - 如需 ServerChan 等运维通知,在 `prod/sendkey.env` 中维护;该文件不被 QuickQuip 应用读取 -当前部署会把: +自动部署会把: - 根 `.env` -- `config/` 目录下的运行配置(如 `llm.toml`、`generation.toml`、`awakening.toml`、`sensitive_words.toml`、`games.toml`) +- `config/` 目录下的运行配置(如 `generation.toml`、`awakening.toml`、`sensitive_words.toml`、`games.toml`) - `skills/` 目录(Skill 系统技能包,从 `skills.example/` 复制预置包或自建;每次部署把其中新增的 Skill 非破坏性合并进服务器部署根共享 `skills/`:已存在文件不被覆盖、删除不传播,已安装 Skill 的更新与删除经 Web 管理页或服务器手动维护) - `llm_about/vocab.yaml` - `llm_about/identities.yaml` @@ -103,12 +101,15 @@ cp -r prod.example prod # prod/ 已存在时会嵌套成 prod/prod.example( ```bash cd /path/to/QuickQuip/prod docker compose --env-file ../.env build quickquip +docker compose --env-file ../.env run --rm --no-deps web-admin python -m quickquip.llm.config_cli init docker compose --env-file ../.env up -d ``` +中间的初始化命令借用 Web 容器的可写配置挂载,建立空配置库或迁移旧 TOML,完成后退出。新安装保持 LLM 关闭,在第 7 步通过后台启用;已有配置库的升级按[初始化与升级](llm-configuration.md#初始化与升级)的停服和备份要求执行。 + 当前 compose 会: -- 不内置 SearXNG:搜索能力需由外部独立 searxng 实例提供,必须在 `.env` 中设置 `QUICKQUIP_SEARXNG_BASE_URL` 指向它(未设置时 compose 启动即报错) +- 通过第 3 步设置的 `QUICKQUIP_SEARXNG_BASE_URL` 连接独立 SearXNG 服务 - 通过 `../.env` 向 bot 和 Web Admin 提供应用环境变量 - 把 `../config` 只读挂载到容器内 `/app/config` - 把部署根共享 `skills/` 挂载到容器内 `/app/skills`(Skill 技能包目录,与 `data/` 同级:`quickquip` 只读、`web-admin` 读写以支撑 Skill 页在线管理;扁平安装时即项目根 `skills/`;目录为空或不存在时 Skill 工具不注册,详见 [skills.md](skills.md)) @@ -123,9 +124,9 @@ docker compose --env-file ../.env up -d 补充说明: - **`DRIVER` 以 `.env` 为最终生效值**:compose 的 `environment:` 插值与 `env_file:` 都会读到同一份 `.env`,在其中写 `DRIVER=~fastapi` 会同时穿透两层覆盖模板默认。要让 QuickQuip 正向 WebSocket 连接协议端(`ONEBOT_WS_URLS` 指向适配器的 WS 服务端,当前默认模板为 `ws://llbot:3001/`),`DRIVER` 必须是 `~fastapi+~websockets`(纯 `~fastapi` 无 WS client 能力,`ONEBOT_WS_URLS` 会被忽略并告警)。替代拓扑:在适配器管理界面启用反向 WS 指向 QuickQuip 的 `ws://:8080/onebot/v11/ws`,此时 QuickQuip 侧不需要 WS client(连接拓扑详见 [onebot-adapters.md](onebot-adapters.md))。deploy 脚本在 `prod/llbot-data` 存在时会自动把 `ONEBOT_ACCESS_TOKEN` 同步进 LLBot 反向 WS 配置,正反拓扑可并存。 -- `config/llm.toml`、`config/awakening.toml`、`llm_about/vocab.yaml`、`llm_about/identities.yaml` 及群级覆盖文件虽然是 bind mount,但 `quickquip` 会在进程启动时把它们读入内存;`awakening.toml` 是这些文件中唯一的例外:bot 每 30 秒检测其 mtime,外部修改会自动重载 +- Bot 和 Web 共享本机 `data/llm_config.db`。手动安装使用上面的初始化命令准备配置与独立资产;已有部署升级按[迁移步骤](llm-configuration.md#初始化与升级)执行。部署脚本保留生产库,本地配置库和旧 TOML 由各自的配置运维流程管理。Bot 重启读取已发布快照,草稿通过后台单独应用。Bot 每 30 秒检查 `awakening.toml` 的修改时间并重载。 - 部署脚本在切换发行目录后强制重建应用容器一次,使源码和配置挂载指向本次发行目录 -- 如果只是在线微调配置而不走部署脚本,也可以在群里手动执行 `/llm reload`;重载后会探活当前群实际生效的 provider/model,探活会发一条 max_tokens=1 的真实请求,可能产生 provider 计费 +- 在线调整 LLM 配置通过 Web Admin“LLM 配置”保存草稿并应用,等待任务完成及 Bot 实际版本确认。修改价格等无关字段保持 MCP 连接;MCP 改动的连接状态独立回报。`/llm reload` 重载已发布配置及相关独立资产,并探活当前模型,可能计费。 ### 4.1 首次准备贴吧登录态 @@ -152,7 +153,7 @@ data/tieba/storage_state.json 容器化部署时,`data/fonts/` 目录应通过 `data/` bind mount 挂载到容器内,字体文件上传一次后即可持久使用。若字体文件缺失,执行 `/wordcloud` 时 bot 会回复明确的错误提示;SVG 画图则回退系统字体,精简系统上中文可能渲染为方框,建议同样放置该文件。 -SVG 画图的部署边界: +SVG 画图的依赖与平台差异: - 渲染引擎 resvg 以 pip 依赖随 `requirements.txt` 安装,Docker 镜像与 Windows 懒人包均随依赖安装自动获得,无需额外系统依赖或构建步骤。 - 文本渲染优先使用上述 NotoSansSC 字体;emoji 等字符依赖系统字体回退。官方 Playwright 基础镜像自带常用字体(含彩色 emoji),Docker 部署一般无需处理;裸机源码部署在精简系统上可能缺少 emoji 字体,图中 emoji 会显示为方框;Windows 使用系统字体(微软雅黑、Segoe UI Emoji),一般无需处理。 @@ -167,13 +168,19 @@ SVG 画图的部署边界: docker compose --env-file ../.env logs -f llbot ``` -日志中会出现二维码或登录链接,用手机 QQ 扫码确认;也可通过 WebUI(`http://<服务器IP>:3080`)扫码。登录成功后,LLBot 登录态持久化在 `llbot-qq/` 目录,配置在 `llbot-data/` 中。 +日志中会出现二维码或登录链接,用手机 QQ 扫码确认。模板的 WebUI 端口绑定服务器本机;需要浏览器操作时,在本地终端建立 SSH 隧道,将 `user@server` 替换为服务器账号与地址: + +```bash +ssh -N -L 3080:127.0.0.1:3080 user@server +``` -**镜像版本与 pin**:compose 模板固定使用 `initialencounter/llonebot:v7.12.14-7.3.2-45758`,不使用 `latest`——原因与更换版本的注意事项见 [onebot-adapters.md](onebot-adapters.md) 的 LLBot profile。 +保持隧道运行,在本地浏览器打开 `http://127.0.0.1:3080` 扫码。登录态持久化在 `prod/llbot-qq/`,配置在 `prod/llbot-data/`。 -**WebUI 启用 OneBot 对接**:新部署的 LLBot 四种网络对接方式(正向 WS / 反向 WS / HTTP / HTTP 上报)默认全部关闭。在 WebUI(`http://<服务器IP>:3080`)里启用所需方式——正向 WebSocket(服务端)的 token 为必填项,须与根目录 `.env` 的 `ONEBOT_ACCESS_TOKEN` 同值,QuickQuip 侧才连得上。 +**镜像版本与 pin**:compose 模板固定使用 `initialencounter/llonebot:v7.12.14-7.3.2-45758`,版本选择与升级事项见 [onebot-adapters.md](onebot-adapters.md) 的 LLBot profile。 -**重启与快速登录**:`llbot-qq/` 登录态目录完好时,容器重启后 LLBot 可能走快速登录(`QUICK_LOGIN_QQ` 生效,免扫码),也可能要求重新扫码——快速登录存在时效性(验收中两种情况都出现过)。无论哪种,优先 `docker compose restart llbot` 而不是重建容器或删除 `llbot-qq/`;扫码后若消息无响应且日志出现 `getSelfNick` 等 TypeError,restart 一次触发快速登录即可恢复。 +**WebUI 启用 OneBot 对接**:新部署的 LLBot 四种网络对接方式(正向 WS / 反向 WS / HTTP / HTTP 上报)默认全部关闭。在上述 WebUI 中启用所需方式——正向 WebSocket(服务端)的 token 为必填项,须与根目录 `.env` 的 `ONEBOT_ACCESS_TOKEN` 同值,QuickQuip 侧才连得上。 + +**重启与快速登录**:`llbot-qq/` 登录态目录完好时,容器重启后 LLBot 可能走快速登录(`QUICK_LOGIN_QQ` 生效,免扫码),也可能要求重新扫码——快速登录存在时效性(验收中两种情况都出现过)。登录异常时先用 `docker compose --env-file ../.env restart llbot` 重启,保留 `llbot-qq/` 登录态;扫码后若消息无响应且日志出现 `getSelfNick` 等 TypeError,restart 一次触发快速登录即可恢复。 ### 6. 验证运行 @@ -185,7 +192,7 @@ docker compose --env-file ../.env ps docker compose --env-file ../.env logs -f quickquip ``` -在群里发一条“早安”,如果 bot 回复了时区猜测,说明部署成功。 +在群里发送“早安”,收到时区猜测回复即确认基本群消息收发与规则响应可用。启用的 LLM 和其他功能按下方检查。 如果还启用了贴吧功能,可以继续验证: @@ -196,25 +203,33 @@ docker compose --env-file ../.env logs -f quickquip ### 7. Web 管理后台 -compose 会同时启动 `web-admin` 容器(`python web_api.py`,容器内监听 `0.0.0.0:5104`,宿主侧仅绑定 `127.0.0.1:5104`)。通过 nginx 反代后即可打开管理界面,提供: +compose 会同时启动 `web-admin` 容器(`python web_api.py`,容器内监听 `0.0.0.0:5104`,宿主侧绑定 `127.0.0.1:5104`)。首次管理在**自己电脑的另一个终端**建立隧道,将 `user@server` 替换为服务器账号与地址: + +```bash +ssh -N -L 5104:127.0.0.1:5104 user@server +``` + +保持隧道窗口运行,在自己电脑的浏览器打开 `http://127.0.0.1:5104/ops/`,输入第 3 步设置的 `WEB_ADMIN_PASSWORD`。进入概览后,按[首次启用 LLM](llm-configuration.md#首次启用-llm)添加一个渠道和模型、保存并应用,等待 Bot 加载,再在测试群开启 AI 并收到第一条回复。 + +后台提供: - 消息统计(各群消息数、活跃用户、规则触发次数) - 群级规则开关(toggle 开关,实时生效) - 每日总结 / 每日播报群组管理 -- `config/llm.toml`、`config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 在线编辑(保存前校验 TOML 语法) +- LLM 结构化配置、保存/应用、JSON 导入预览、实际 Bot 状态和配置任务 +- `config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 在线编辑(保存前校验 TOML 语法) - 敏感词过滤器只读状态查看;`config/sensitive_words.toml` 只通过服务器本地文件或部署流程维护,不在 Web Admin 中回显或编辑 - 记忆、对话、人格、资料、唤醒、LLM 用量、MCP、贴吧、词云、语录、调度器监控、审计、金币经济和牛牛面板 - 实时日志 / LLM Trace / 日志归档面板(日志读取 `../data/logs`,LLM HTTP 调用索引和正文读取 `../data/llm_trace.db`) -管理界面同时有两层门: +长期通过 HTTPS 反向代理访问时,按 [Web Admin 的代理说明](web-admin.md#反向代理注意事项)配置站点。配置 nginx `auth_basic` 的部署有两层门: - nginx `auth_basic`:外层站点访问控制,密码文件位置由你的反代配置决定 - QuickQuip Web Admin session:应用层登录,会读取 `WEB_ADMIN_PASSWORD` 并在浏览器里建立 `HttpOnly` session cookie -建议在根目录 `.env` 中补充: +后台登录口令已在第 3 步设置,其他登录参数可在根 `.env` 调整: ```env -WEB_ADMIN_PASSWORD=change-this-admin-password WEB_ADMIN_SESSION_TTL_HOURS=168 WEB_ADMIN_COOKIE_SECURE=auto ``` @@ -226,17 +241,56 @@ WEB_ADMIN_COOKIE_SECURE=auto | 宿主路径 | 容器路径 | 权限 | |---|---|---| | `../data` | `/app/data` | 读写 | -| `../config` | `/app/config` | **读写**(llm.toml 在线编辑需要) | +| `../config` | `/app/config` | **读写**(独立配置在线编辑与首次迁移资产提取) | | `../llm_about` | `/app/llm_about` | **读写**(资料页在线编辑需要) | | `${QUICKQUIP_ROOT:-..}/skills`(扁平安装即 `../skills`) | `/app/skills` | **读写**(Skill 页在线安装/编辑/预置同步落盘) | | `../frontend/dist` | `/app/frontend/dist` | 只读 | | `../web_api.py` | `/app/web_api.py` | 只读 | | `../src` | `/app/src` | 只读(hybrid 源码热更新) | -> 注意:`quickquip` 容器的 `config`、`llm_about` 与共享 `skills/` 挂载仍可保持只读(`:ro`),只有 `web-admin` 需要写权限。 +> 注意:`quickquip` 容器的 `config`、`llm_about` 与共享 `skills/` 挂载保持只读(`:ro`),只有 `web-admin` 需要写权限。 > `config/sensitive_words.toml` 即使位于同一挂载目录,也不会通过 Web Admin 配置编辑器读取或写入。 -### 代码更新 +## 自动部署与版本回滚 + +日常部署推荐使用 `prod/deploy-v4.sh`(Bash)或 `prod/deploy-v4.ps1`(PowerShell)。两端共享远端事务执行器,按清单上传应用文件,预检和构建成功后切换版本目录,并验证容器、OneBot 连接与 Web Admin HTTP。完整前提、参数与目录结构见 [生产模板说明](../../prod.example/README.md)。服务器需具备 rsync、flock、Python ≥ 3.11.8 和 Docker Compose ≥ 2.27。 + +执行前,`prod/` 已从 `prod.example/` 复制,根 `.env` 和所选功能配置已填写,`quickquip-prod` 已替换为自己的 SSH 连接名称。首次准备见第 3 步;日常命令位于[自动部署操作](#自动部署操作)。 + +已有平铺部署首次使用 `--migrate`:先保存服务器上的旧代码与运行镜像作为基线,再部署候选版本。新服务器首次尚未扫码时可显式使用 `--skip-health`,之后完成扫码与健康核验。PowerShell 使用同名参数,指定回滚版本时使用 `-Rollback -ReleaseId `;完整接口见 `bash prod/deploy-v4.sh --help`。 + +每次发布携带一个版本标识:部署的 `pyproject.toml` 版本加上服务器镜像构建完成时刻(如 `1.15.3-dev.2+build.20260909.065235`),出现在事务日志的 `image built` 与 `release complete` 行中,便于将线上 release 目录与代码版本对上号。 + +部署失败时自动恢复本次修改的共享文件并验证旧版本健康;手动回滚保留当前根 `.env`、数据库与部署根共享 `skills/`(Skill 安装成果不随代码版本回退;目标 compose 使用共享目录时,回滚把目标 release 中新增的 Skill 非破坏性合并进共享目录)。回滚到挂载 release 自带 `skills/` 副本的历史版本时,该版本继续使用其目录内副本。数据迁移与外部副作用不随代码回滚,部署前应核对版本升级说明。`--dry-run` 会本地构建前端,PowerShell 还会临时打包,两者均不连接远端。 + +配置备份使用 `python -m quickquip.llm.config_cli backup data/llm-config-backup.db` 的 SQLite backup 接口。恢复数据库前停止 Bot 和 Web,先备份当前库,再运行 `restore --file <备份路径> --services-stopped`;恢复会清除旧运行租约与心跳。配置回流、导入预览、应用及备份恢复可使用模板的 Bash/PowerShell 配置运维驱动,完整命令见 [LLM 配置管理](llm-configuration.md)。 + +回退至 1.16.1 使用首次迁移前的 TOML 及人格/多模态资产备份;升级后新增的配置不会反向转换成 TOML。新库保留供再次升级,回退应同时核对运行代码与迁移前配置资产。升级与回退前还需备份会话库和追踪库;旧代码运行后再次升级的 Session 身份维护步骤见[回退旧版本后再次升级](llm-configuration.md#回退旧版本后再次升级)。 + +运行态位于部署根目录的 `data/`、`skills/` 和 `prod/`,版本内容位于 `releases//`,`current` 与 `previous` 指向当前和前一版本。运维命令需按 [模板中的手动访问步骤](../../prod.example/README.md#manual-compose-access) 导出部署根目录和版本标识。手动平铺安装按上方 1–7 步进行,版本目录部署由脚本管理。 + +### 自动部署操作 + +以下命令在**本地项目根目录**执行。首次准备见第 3 步及[SSH 设置](../../prod.example/README.md#ssh-connection);`quickquip-prod` 使用自己的连接名称。先单独运行预览,确认上传内容,再执行部署: + +```bash +bash prod/deploy-v4.sh --dry-run +``` + +```bash +bash prod/deploy-v4.sh --host-alias quickquip-prod +bash prod/deploy-v4.sh --status --host-alias quickquip-prod +``` + +新服务器首次需要扫码时,首次部署使用 `--skip-health`,之后按第 5、7 步登录,并按[生产模板的首次健康检查](../../prod.example/README.md#commands)核验。版本目录服务器执行 Compose 命令前,按[手动访问说明](../../prod.example/README.md#manual-compose-access)设置路径与版本变量;上面的手动安装命令使用平铺目录。 + +回滚由需要恢复上一版本的部署单独执行: + +```bash +bash prod/deploy-v4.sh --rollback --host-alias quickquip-prod +``` + +## 代码更新 项目采用 **hybrid 混合模式**部署: @@ -262,7 +316,7 @@ WEB_ADMIN_COOKIE_SECURE=auto - 只改了 `frontend/dist`(前端静态文件)时,`docker restart quickquip-web-admin` 即可,无需重建。 -**历史数据回灌(可选)**:`scripts/` 随镜像分发两个一次性回灌脚本——`backfill_record_identities.py` 把存量会话与语录回灌为群级身份候选(专文见 [record-identities.md](record-identities.md));`backfill_chat_archive.py` 把 1.15.2 之前退役的旧每日消息 / 词云 JSONL(`data/daily_msgs/`、`data/wordcloud_msgs/`)导入聊天记录归档库 `data/chat_archive.db`(导入完成后旧 JSONL 目录方可清理)。服务器容器内运行: +**历史数据回灌(可选)**:`scripts/` 随镜像分发两个一次性回灌脚本——`backfill_record_identities.py` 为存量记忆、语录和留言补齐正文片段与成员引用索引(专文见 [record-identities.md](record-identities.md));`backfill_chat_archive.py` 把 1.15.2 之前退役的旧每日消息 / 词云 JSONL(`data/daily_msgs/`、`data/wordcloud_msgs/`)导入聊天记录归档库 `data/chat_archive.db`(导入完成后旧 JSONL 目录方可清理)。服务器容器内运行: ```bash docker compose --env-file ../.env exec -T quickquip python scripts/backfill_chat_archive.py --dry-run @@ -272,6 +326,14 @@ docker compose --env-file ../.env exec -T quickquip python scripts/backfill_chat ## 日常维护 +修改根 `.env` 或 Compose 配置后,在实际 Compose 所在目录重新创建服务,使新环境变量和挂载生效。平铺生产部署执行: + +```bash +docker compose --env-file ../.env up -d quickquip web-admin +``` + +版本目录部署先按[手动访问步骤](../../prod.example/README.md#manual-compose-access)设置变量,沿用 `--env-file "$QUICKQUIP_ENV_FILE"`。使用根模板或自定义 Compose 的部署沿用启动时的 `-f` 等参数;仅改后台口令时可只更新 `web-admin`。下面的 `restart` 用于重新加载已挂载的 Python 源码。 + ```bash # 更新 Python 源码后重启(hybrid 模式下无需重建) cd /path/to/QuickQuip/prod @@ -295,7 +357,7 @@ docker compose --env-file ../.env down 不需要。 -如果未来要给 QuickQuip 接 MCP,应该把 MCP 视为 QuickQuip 自己的外部工具后端。当前项目已经支持把 Codex 里常用的 Docker 型 MCP server 镜像到 `config/llm.toml`,应用密钥统一写入根 `.env`,宿主路径和 sidecar 编排留在私有 `prod/` 中维护。 +QuickQuip 通过模型 API 提供对话,通过 MCP 接入外部工具服务。将 MCP 服务配置到 `settings.mcp.servers` 后保存并应用;凭据写入根 `.env`,宿主路径和 sidecar 编排在私有 `prod/` 中维护。 当前项目通过 SearXNG 提供内置 `search_web` 搜索,通过 MCP 扩展接入 Tavily 等外部工具。MCP 集成的正式约定见 [docs/dev/mcp-integration.md](../../docs/dev/mcp-integration.md)。 @@ -311,7 +373,7 @@ docker compose --env-file ../.env logs -f llbot # 找新的二维码 ### QQ 风控/冻结 - 新注册的 QQ 号容易被风控,建议用有一定使用历史的号 -- 海外 IP 更容易触发风控,国内服务器会稳定很多 +- 登录地点、IP 或设备变化后观察账号状态,必要时按 QQ 提示恢复登录 - 避免短时间内大量发消息 ### 端口冲突 @@ -322,11 +384,11 @@ docker compose --env-file ../.env logs -f llbot # 找新的二维码 优先检查以下几项: -- `config/llm.toml` 是否存在且内容正确 -- `.env` 中是否填了 `OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`GEMINI_API_KEY` +- 配置库 schema 是否受支持;草稿是否已单独应用,published_revision 与 Bot loaded_revision 是否一致,心跳是否新鲜 +- `.env` 中是否填了当前所选渠道的密钥环境变量 - `.env` 中是否填了 `QQ_ACCOUNT` 以及启用 MCP 时需要的 API key - 搜索服务是否运行,QuickQuip 容器内是否能访问配置里的 `SEARXNG_BASE_URL` - `llm_about/identities.yaml` 是否存在且格式正确;如只使用群级覆盖,也确认 `llm_about/{群号}/identities.yaml` 存在。文件缺失(INFO)、存在但为空模板(WARNING)、正常加载(`已加载 N 条身份`)在 bot 日志中均有对应记录,可据此核对身份索引是否生效 - `docker compose --env-file ../.env logs -f quickquip` 中是否出现配置文件缺失或 API key 缺失提示 -- 如果文件内容已经更新,但 `/llm personas`、`/llm providers` 或词表行为仍旧是旧版本,先执行 `/llm reload`,或确认部署脚本是否已经把 `quickquip` 容器重建 +- 如果渠道/模型修改未生效,检查原应用任务的阶段与错误,并确认 Bot 心跳;单独重启不会应用草稿。人格或词表文件修改通过 `/llm reload` 或对应独立重载入口生效。 - `/llm reload` 会在重载后探活当前群实际生效的 provider/model;如需全量巡检,在群内执行 `/llm probe` 或在 Web Admin 诊断页点击“探活 Provider”,会对所有已配置 provider 各发一次 max_tokens=1 的真实请求,可能产生 provider 计费 diff --git a/skills.example/self-docs/references/docs-admin-game-config.md b/skills.example/self-docs/references/docs-admin-game-config.md index 9b9bae9e..6d3d261c 100644 --- a/skills.example/self-docs/references/docs-admin-game-config.md +++ b/skills.example/self-docs/references/docs-admin-game-config.md @@ -6,50 +6,23 @@ --- -## 系统架构 +## 游戏操作与管理权限 -游戏系统由三层组成: +`/game list` 查看已注册的数字炸弹、21 点和俄罗斯轮盘,`/game stop` 结束本群正在进行的游戏。这两个命令向群友开放。牛牛使用独立命令,玩法见[群内小游戏](../user/group-games.md)。 -``` -金币经济 (game_economy.db) ← SQLite,签到 / 金币账户 / 转账 - ├── BaseGame 游戏 ← 21 点 / 俄罗斯轮盘 / 数字炸弹(session 型) - └── NiuNiu RPG ← 牛牛大作战(持久化,niuniu.db) -``` - -所有数据存储在 `data/` 目录下,gitignore 排除。游戏模块代码在 `src/quickquip/games/` 下。 +结束 21 点时,发牌前取消会退还赌注,发牌后结束保留已扣赌注。俄罗斯轮盘在准备阶段尚未扣款,游玩阶段取消会退还双方赌注。 ---- - -## 群管理员命令 +群管理员可用 `/rules` 查看已登记规则,再用 `/disable ` 或 `/enable ` 调整自定义接龙等规则的回复。内置数字炸弹、21 点、俄罗斯轮盘和牛牛没有对应的规则总开关。 -### 游戏进程控制 - -| 命令 | 说明 | -|------|------| -| `/game list` | 查看本群当前可用的游戏列表 | -| `/game stop` | 强制结束本群正在进行的游戏 | -| `/disable ` | 禁用某条游戏规则 | -| `/enable ` | 启用某条游戏规则 | - -### 金币管理(预留) - -当前版本金币系统仅支持签到获取,管理员暂无可直接增减金币的群内命令。可通过 Web Admin 的数据管理或直接操作 SQLite 调整。 +金币管理通过 Web Admin“金币”页进行:选择群和账户,填写增减金额及原因,操作写入审计日志。群内金币命令用于签到、余额和排行查询。 --- ## 部署者配置 -### 游戏总开关 +### 配置范围 -游戏注册在 `src/quickquip/app/message_pipeline.py` 中。要禁用某个游戏,注释掉对应的 `game_registry.register()` 行: - -```python -# 当前注册的游戏 -game_registry.register(NumberBombGame(config=games_config.number_bomb)) # 数字炸弹 -game_registry.register(BlackjackGame(economy=game_economy, config=games_config.blackjack)) # 21 点 -game_registry.register(RussianRouletteGame(economy=game_economy, config=games_config.russian_roulette)) # 俄罗斯轮盘 -# NiuNiu 不走 GameRegistry,删除 niuniu_store 行即可禁用 -``` +`config/games.toml` 提供经济和游戏参数,当前没有全局或按群停用游戏的配置项。结束当前局使用 `/game stop`;调整游戏实现和注册方式的开发说明见[游戏框架](../dev/game-framework.md)。 ### 游戏参数调整 @@ -76,8 +49,8 @@ game_registry.register(RussianRouletteGame(economy=game_economy, config=games_co | `[niuniu]` | `fenced_protection` | 300 | 被击保护期(秒) | | `[niuniu]` | `glue_cooldown` | 180 | 打胶 CD(秒) | | `[niuniu]` | `unsubscribe_gold` | 500 | 注销费用 | -| `[niuniu]` | `decay_rate_high` | 0.01 | 高长度衰减率(\|length\| > 50);正侧按此率、负侧减半 | -| `[niuniu]` | `decay_rate_normal` | 0.005 | 正常衰减率(\|length\| ≤ 50) | +| `[niuniu]` | `decay_rate_high` | 0.01 | 长度 > 50 时按此率缩短;长度 < -50 时,绝对值按此率的 0.4 倍缩小 | +| `[niuniu]` | `decay_rate_normal` | 0.005 | 长度在 -50 至 50 时:正侧按此率缩短,负侧绝对值按此率的 0.8 倍增加 | | `[niuniu]` | `luck_sigma` | 1.0 | 打胶运势对数标准差(lg(x) ~ N(0, σ)) | | `[niuniu]` | `fence_luck_sigma` | 1.0 | 击剑运势对数标准差(同上分布) | | `[niuniu]` | `luck_power` | 0.75 | 运势幂压缩指数(luck^0.75:中位运势行为不变,仅温和化极端运势的实际影响) | @@ -139,18 +112,18 @@ QuickQuip 内置两套牛牛文案预设,通过 TOML 文件驱动,支持按 ### 游戏无响应 1. 确认游戏是否注册成功:机器人启动时会输出已注册的游戏列表 -2. 检查本群是否已有进行中的游戏:`/game stop` 强制结束 -3. 确认群规则开关没有禁用该游戏:`/rules` 查看 +2. 检查本群是否已有进行中的游戏;需要结束当前局时使用 `/game stop` +3. 自定义接龙检查 `/rules` 中的规则状态;内置游戏检查启动日志和命令返回的错误 ### 金币异常 1. 金币数据在 `data/game_economy.db`,可用任意 SQLite 浏览器查看 -2. 所有金币操作都有原子事务保护(`BEGIN IMMEDIATE`) -3. 转账失败会自动回滚,不会出现“一方扣了一方没加”的情况 +2. 在 Web Admin“审计”页检查人工余额调整,在游戏记录中核对结算 +3. 金币转移在同一事务中执行;失败操作会回滚,可根据日志检查原因 ### NiuNiu 数据问题 1. 用户数据在 `data/niuniu.db` 的 `niuniu_users` 表 2. 操作记录在 `niuniu_records` 表,可用于排查异常长度变化 -3. CD 状态存储在内存中,重启机器人后 CD 全部重置 +3. 动作冷却存储在内存中,重启机器人后清空冷却状态 4. 文案模式切换通过 `/牛牛文案 <模式名>` 命令或 Web Admin 牛牛面板操作,数据存储在 `niuniu_group_text` 表 diff --git a/skills.example/self-docs/references/docs-admin-global-admins.md b/skills.example/self-docs/references/docs-admin-global-admins.md index 7864999d..efc00eab 100644 --- a/skills.example/self-docs/references/docs-admin-global-admins.md +++ b/skills.example/self-docs/references/docs-admin-global-admins.md @@ -2,18 +2,13 @@ # 全局管理员 -全局管理员是跨群、只认 QQ 号的管理身份,用于在 bot 所在的任何群使用 -管理员命令——即使你在该群不持有群主/群管理角色。它也是后续高危工具 -(容器内 Shell 等)的权限底座:这类工具将仅对全局管理员开放,群主与 -群管理不可用。 +全局管理员按 QQ 号识别,可在机器人所在的任何群使用要求管理员权限的命令。部署者在 `config/admins.toml` 维护名单;群主与群管理继续使用 QQ 群角色授权。 ## 权限关系 -- 引入高危工具前,全局管理员的权限面与群主/群管理等价:所有要求 - 「管理员及以上」的斜杠命令(唤醒开关、日报、总结、词云、规则管理、 - 牛牛文案模式等)对全局管理员开放。 -- 同一用户既是群管理又是全局管理员时,按全局管理员对待。 -- 私聊不适用:管理命令仍要求群聊内发起。 +- 要求“管理员及以上”的群聊命令对全局管理员开放,例如唤醒开关、日报、总结、词云、规则管理和牛牛文案模式。 +- 同一用户具有群管理和全局管理员身份时,按全局管理员处理。 +- 群聊管理命令在群内发起;私聊功能按各自命令的权限规则执行。 ## 配置 @@ -27,7 +22,7 @@ global_admins = [ - 条目为 QQ 号数字字符串;非法条目会被跳过并在日志中告警。 - 修改后约 5 秒内热重载生效,无需重启。 -- 文件缺失或列表为空表示功能关闭,行为与未引入时一致。 +- 文件缺失或列表为空时,全局管理员名单为空,群角色授权照常生效。 - 文件损坏或格式错误时保留上次有效名单,修复文件即可恢复。 ## 审计 @@ -35,11 +30,8 @@ global_admins = [ 应用日志中的 `ADMIN_TRACE` 行记录两类事件,便于回溯: - `registry_loaded`:注册表加载或热重载生效,含当前名单。 -- `global_admin_unlock`:管理员门禁检查中,全局管理员身份越过群角色 - 边界放行时(群角色本就足够时不会记录)。 +- `global_admin_unlock`:管理员门禁检查中,全局管理员身份提供授权时(群角色本就足够时不会记录)。 ## 与 NoneBot 超用户的关系 -QuickQuip 不使用 NoneBot 框架内置的 SUPERUSERS 机制;全局管理员的 -唯一配置来源是 `config/admins.toml`,请勿通过 `.env` 的 `SUPERUSERS` -另行配置,避免出现两套权限真相。 +QuickQuip 的全局管理员唯一配置来源是 `config/admins.toml`。NoneBot 的 `SUPERUSERS` 设置对这些管理命令的授权无效,请在管理员名单中填写 QQ 号。 diff --git a/skills.example/self-docs/references/docs-admin-llm-configuration.md b/skills.example/self-docs/references/docs-admin-llm-configuration.md new file mode 100644 index 00000000..a347871c --- /dev/null +++ b/skills.example/self-docs/references/docs-admin-llm-configuration.md @@ -0,0 +1,141 @@ + + +# LLM 配置数据库操作 + +本文面向部署者,说明 LLM 配置的初始化、修改、生效及备份恢复。配置保存在 `data/llm_config.db`。渠道、模型、别名、请求参数、风格、价格、级联与全局设置通过 Web Admin 的“LLM 配置”页面维护。JSON 交换示例见 [`config/llm.json.example`](../../config/llm.json.example),字段类型、继承规则与范围见受保护接口 `/ops/api/llm-configuration/metadata`,开发者接口契约见 [API 文档](../dev/llm-configuration-api.md)。 + +页面包含渠道与模型、对话与上下文、工具与技能、报告与路由、模型价格、发布与历史六个模块。渠道模块包含模型参数与回复风格,工具模块包含 MCP 状态,发布模块提供 JSON 导入导出与历史管理。顶部状态条展示草稿→已发布→Bot 运行的版本管道;渠道与模型的参数覆盖以“跟随上层/覆盖”表达继承,渠道详情内可直接查看生效参数与发起模型探测。 + +## 首次启用 LLM + +目标是让机器人在测试群回答一次提问。开始前按 [README 的安装步骤](../../README.md#安装步骤)或[云端部署指南](deployment.md)启动 Bot 和 Web Admin,并确认机器人能收到群消息。你需要后台管理口令,以及一个模型渠道提供的协议、API 地址、模型名和 API key。 + +1. **准备人格与密钥。** 首次安装将 `config/personas.example/` 复制为 `config/personas/`,使用其中的 `quickquip-default` 人格;Windows 懒人包已在首启创建。根 `.env` 填写所选渠道的密钥,例如 `OPENAI_API_KEY=你的密钥`。服务运行后修改 `.env` 时,源码或懒人包先停止 Bot 和 Web,再重新启动;容器按[环境变量更新步骤](deployment.md#日常维护)重建这两个服务,使新值进入容器。 +2. **登录并添加渠道。** 打开后台,进入“LLM 工坊 → LLM 配置 → 渠道与模型”,点击“添加渠道”。“渠道 ID”可填 `my-provider`;“协议”和“API 地址”按渠道服务商的说明填写;“密钥环境变量”填写第 1 步使用的变量名,例如 `OPENAI_API_KEY`。保持渠道已启用。 +3. **添加模型并选为默认。** 在“模型清单”点击“添加模型”,填写服务商给出的模型名并保持启用;回到“连接与认证”,在“默认模型”中选中它,再点击“设为默认渠道”。模型的上下文容量按服务商说明填写;留空时由系统识别,无法识别的模型需手动填写。 +4. **开启全局 LLM。** 进入“对话与上下文”的“总开关与全局默认”,打开“启用 LLM 功能”,确认“默认渠道”为刚添加的渠道,“默认人格”为 `quickquip-default`。 +5. **保存并应用。** 点击页面顶部“保存草稿”,再点击“应用到 Bot”。等待任务成功,确认顶部“Bot 实际运行”与“已发布版本”一致,心跳为新鲜状态。Bot 每 5 秒领取配置任务,运行它的终端或容器应保持启动。 +6. **在测试群提问。** 由群主、群管理员或[全局管理员](global-admins.md)先发送 `/llm on`,再发送 `/llm status` 确认本群 AI 已开启。随后发送 `/ai 请回复“测试成功”`,收到模型回答即完成这条路线。`/ai` 是默认前缀;已自定义前缀的群使用实际设置。提问会发出真实模型请求,按渠道计费。 + +应用任务一直排队时,先确认 Bot 正在运行并与后台共享配置数据库;任务失败时查看该任务的错误,核对密钥变量、默认模型和人格文件。配置已加载而群内没有回复时,先看 `/llm status` 与触发前缀,再查 Bot 日志中的渠道请求错误。 + +## 初始化与升级 + +源码安装在项目根目录执行: + +```bash +python -m quickquip.llm.config_cli init +``` + +初始化检查 `config/llm.toml`。有旧文件时只迁移一次;没有旧文件时建立关闭 LLM、无渠道的配置。已有数据库始终具有权威性,库损坏或 schema 不支持会明确报错。Windows `start.bat` 在启动服务前执行初始化;根 `docker-compose.example.yml` 的 `config-init` 在可写配置挂载中完成迁移,bot 和 Web 等待该服务成功后启动。生产部署脚本在切换前初始化服务器配置;手动生产安装先构建镜像,再按[部署指南第 4 步](deployment.md#4-启动服务)借用 Web 容器运行初始化命令。 + +配置数据库当前为 schema 3:schema 2 增加请求指纹 Profile 字段,schema 3 增加版本标记(修订保存时间与备注)并把价格统一为按渠道计费。初始化会事务性升级已验证的 schema 1 或 2,保留草稿修订、已发布快照和操作记录。旧 schema 的配置库需要先停止旧 Bot 与 Web、备份数据库,再执行 `python -m quickquip.llm.config_cli init --services-stopped`。普通 Web 请求与部署准备中的初始化会拒绝直接升级这类库,保持旧程序可用。升级后的数据库需要支持 schema 3 的程序。回退旧程序时使用升级前备份。旧 schema 的历史备份可先复制到独立目录,运行新程序的 `init --services-stopped` 升级副本,再按恢复流程导入。升级到 schema 3 时,价格表中的全渠道兜底行会归属到渠道或移除,逐条结果写入升级报告,规则见下文价格。Profile 选择缺省为 `default`,自有品牌需要显式启用,配置见[请求指纹 Profile](client-fingerprints.md)。 + + +迁移保留认证变量引用。发现认证字面量时,先将密钥写入根目录 `.env` 并把字段改为 `${VARIABLE}`。`api_key_env` 保存环境变量名称。认证 Header 可使用 `Bearer ${VARIABLE}`。数据库、导出、日志和操作错误不包含展开的认证值。 + +迁移备份与报告存放在 `data/llm-config-migration/<源文件摘要>/`。报告列出字段转换、限制调整、移除和外部资产处理结果。内联人格写入 `config/personas/migrated-*.toml`,使用 `inherit_shared = false` 保持原提示应用语义;已有独立人格按加载优先级保留。已有 `config/generation.toml` 继续提供多模态配置,缺失时迁移提取旧段。原文件始终保留。文件冲突需要查看报告并处理后重新初始化。 + +### 首次迁移的 Profile 字段冲突 + +旧 TOML 的 `extra_body` 或 Header 可能覆盖新版默认 Profile 管理的字段,例如 Claude 的 `system`/`messages`/`tools`、Gemini 的 `contents`/`systemInstruction`/`tools`、Responses 的 `prompt_cache_key`,以及自动 Session Header。初始化返回 `profile_owned_body` 或 `profile_owned_header` 时,数据库尚未准备完成,须先处理迁移输入: + +1. 停止旧服务,保留旧 TOML、人格、多模态资产及数据目录备份,认证字面量先转为根 `.env` 的变量引用。 +2. 根据错误字段路径,从用于迁移的 TOML 中移除冲突的手工字段,保留原值于备份;重新运行 `python -m quickquip.llm.config_cli init`。 +3. 启动新版 Bot 与 Web 后,在渠道详情选择兼容策略:Claude/Gemini 可选 `legacy-claude`/`legacy-gemini`;其他情况按[自定义 Profile](client-fingerprints.md#编辑-profile-文件)定义未管理该字段的策略,并在放置模板后重启 Bot 与 Web。 +4. 将需要使用的手工字段填回渠道 `parameters.extra_body` 或固定 `headers`,预览实际变更,保存草稿并应用,确认 Bot 已加载。认证 Header 继续使用变量引用。 + +旧 TOML 加入 `client_fingerprint` 不会迁移为渠道选择;Profile 选择通过新版配置维护。原备份用于旧版本回退,迁移输入的调整与新版配置分别保存。 + +## 保存与应用 + +Web Admin 以“YYYY-MM-DD HH:mm (备注)”标记版本:时间是该修订的保存时刻,备注可在状态条、发布页或历史快照上随时修改。尚未发布的草稿在多次保存间保留备注,发布后的下一次保存从空备注开始。 + +保存增加 `draft_revision`,机器人继续使用已发布快照。应用指定草稿修订号,建立不可变快照并返回配置任务 ID。后续草稿编辑继续允许;已有应用尚未结束时,再次应用返回冲突。只有 bot 已切换并回报实际版本后,任务才成功。 + +`published_revision` 是已接纳的快照,`loaded_revision` 是 bot 实际加载版本。启动和重启加载已发布配置。心跳超过 20 秒未更新会显示过期;Web 的模型或 MCP 实例不会代表 bot 状态。MCP 准备中或部分服务不可用会单独显示。价格修改影响新任务,历史费用与已经开始的任务保持原价格。 + +核心状态包含配置与业务存储的初始化结果。部署使用 `status --require-loaded` 检查新鲜心跳、发布修订加载和核心就绪;业务库不可用时返回失败。MCP 的准备或降级状态单独报告。 + +每次 LLM 任务固定配置与资源至结束。MCP 配置变化后,新任务使用新连接,已开始的任务使用原连接直到结束。报告时间变更替换对应调度,已经开始的报告继续执行,不补发因调整时间错过的消息。配置修订号覆盖配置库;人格文件、Skill 内容、群覆盖与其他业务数据保持各自存储职责。 + +## 模型参数与引用 + +渠道身份、协议、URL、认证变量、Header、备用地址和代理保持渠道级。模型可以覆盖请求参数,包括超时、温度、输出、流式、User-Agent、缓存、原生搜索、Responses profile、思考档位、重试、纪元和预算。 + +有效值顺序为调用或会话覆盖、模型覆盖、渠道默认、具有该字段职责的全局设置、内置默认。覆盖参数 `null` 继承上一层;空字符串、空对象、false 和允许的零值保存明确意图。`extra_body` 整体替换。`style_profile` 选择共享风格,`style_overrides` 保存追加文本,两者独立覆盖。新写入拒绝越界数值。 + +JSON 模型引用使用 `{ "provider_id": "渠道ID", "model": "wire模型名" }`。级联数组顺序就是尝试顺序,`"@default"` 在任务运行时解析默认选择。模型名可以包含 `/`。 + +价格以“渠道 + 配置的模型 ID”为唯一计费单位,`provider_id` 必须是已存在的渠道。用量按请求发出的模型名匹配价格,上游响应改写的模型名(中转去前缀、带日期的快照名等)不影响定价;同名模型在不同渠道需要各自定价,不存在全渠道兜底。免费价格是零,未定价缺少记录,缓存价格 `null` 使用输入单价。导入旧格式(`format_version: 1`)JSON 时,`渠道ID/模型` 形式的兜底价格归属到该渠道;纯模型名兜底价格只在恰好一个尚未定价的渠道配置了该模型时归属,否则移除;导入预览逐条列出结果。 + +Web Admin 的“模型价格”按渠道分组:模型从该渠道的模型清单中选择,未定价模型可点击补行,新行的单价留空、填写后才能保存;可以把一行价格复制到同样配置了该模型的其他渠道。删除渠道时一并删除它的价格。 + +删除被引用对象会返回依赖。停用渠道和模型保留会话与历史记录。群覆盖只允许已发布且可用的渠道与模型;应用前会重新检查已有覆盖。 + +### 迁移群绑定与退役渠道 + +应用返回 `dependency_conflict` 且字段路径为 `group_settings.` 时,检查该群或私聊的渠道/模型覆盖。已关闭的群或私聊仍可保留覆盖,`/llm off` 不会删除引用。替换渠道时按以下顺序操作: + +1. 添加新渠道与模型,保存并应用,使目标成为已发布可用对象。 +2. 在“群 LLM”找到受影响 scope,将渠道/模型改为新目标或跟随默认并保存;私聊也可用 `/llm use [model]` 迁移。需要移除全部覆盖时,先核对页面“清空”将同时重置的设置。 +3. 再停用或删除旧渠道/模型,检查功能路由等其他引用,保存草稿后应用。 + +群覆盖保存后用于后续请求,正在执行的请求使用原绑定与配置。 + +## CLI + +在项目根目录运行,可用全局 `--db <路径>` 指定配置库。JSON 导入上限为 1 MiB。除初始化外,命令需要已有配置库。下方演示从修订 1 导入并应用修订 2;执行前用 `status` 查询当前草稿修订,并替换 `--expected-revision`。任务 ID、快照 ID 和请求标识也须按本次操作填写;同一请求重试时复用请求标识。 + +```bash +python -m quickquip.llm.config_cli validate +python -m quickquip.llm.config_cli export --source published --output llm-export.json +python -m quickquip.llm.config_cli import llm-export.json --expected-revision 1 --preview +python -m quickquip.llm.config_cli import llm-export.json --expected-revision 1 +python -m quickquip.llm.config_cli diff +python -m quickquip.llm.config_cli apply --expected-revision 2 --request-id unique-request-id +python -m quickquip.llm.config_cli status +python -m quickquip.llm.config_cli status --require-loaded +python -m quickquip.llm.config_cli status --operation returned-operation-id +python -m quickquip.llm.config_cli backup data/llm-config-backup.db +python -m quickquip.llm.config_cli restore --snapshot 1 --expected-revision 2 +``` + +导入完整替换草稿,应用需要独立操作。修订冲突时重新读取草稿并审查差异。历史恢复生成新草稿,修订号不倒退。`migrate-legacy --legacy <文件>` 在已有库上需要 `--expected-revision`,只导入草稿。 + +备份使用 SQLite backup API,覆盖活跃 WAL 中的已提交状态。停止 bot 与 Web 后,才能恢复数据库: + +```bash +python -m quickquip.llm.config_cli restore --file data/llm-config-backup.db --services-stopped +``` + +恢复前检查 schema 并备份当前库,恢复后清除旧心跳与未结束任务租约,再启动服务。不要直接复制正在运行的主数据库文件。 + +## 生产与回退 + +生产配置通过 Web 或配置运维驱动变更。普通 deploy-v4 保留服务器配置数据库,上传排除本地 DB 和本地旧 LLM TOML;首次切换选择服务器迁移输入,并在候选构建通过后、启动前初始化。迁移后生成的人格文件随生产部署保留。 + +`prod/config-v1.sh` 与 `prod/config-v1.ps1` 提供 SSH CLI 驱动,支持 `status`、`export`、`preview`、`import`、`apply`、`backup` 和 `restore`,见 [生产模板](../../prod.example/README.md)。配置回流通过 JSON 导出;数据库回流通过一致备份。 + +回退至 1.16.1 使用迁移前 TOML、独立人格与 generation 资产。保留新库便于再次升级;升级后的新配置修改不反向导出为 TOML。部署前核对实际配置来源、服务启动顺序和备份。 + +### 回退旧版本后再次升级 + +升级或回退前,备份配置数据库及业务会话库 `data/llm.db`、追踪库 `data/llm_trace.db`;这些数据库分别保存配置、聊天/存档与 HTTP 观测记录。使用一致备份,或先停止 Bot 与 Web 后备份实际数据目录。配置库的 CLI `backup` 只覆盖 `llm_config.db`。 + +若已经运行过 1.16.2,随后回退旧代码并产生会话操作,再次升级时需重新分配业务会话身份。旧程序不会维护新增身份字段:在停止 Bot 与 Web、备份当前 `data/llm.db` 后,对该会话库执行以下维护 SQL,完成后启动新版: + +```sql +BEGIN IMMEDIATE; +UPDATE agent_scopes SET session_id = NULL, session_created_at = NULL; +UPDATE session_archives SET session_id = NULL, session_created_at = NULL; +COMMIT; +``` + +可在项目根通过 Python 执行,无需另外安装 SQLite 命令行工具: + +```bash +python -c "import sqlite3; c = sqlite3.connect('file:data/llm.db?mode=rw', uri=True); c.executescript('BEGIN IMMEDIATE; UPDATE agent_scopes SET session_id = NULL, session_created_at = NULL; UPDATE session_archives SET session_id = NULL, session_created_at = NULL; COMMIT;'); c.close()" +``` + +Windows 懒人包将 `python` 换为 `python\python.exe`;容器部署在挂载真实数据目录的维护环境中执行,并核对数据库路径。该操作用于已经包含身份字段的会话库,保留消息、存档、设置和 generation;活动聊天与恢复的存档在新版中重新懒分配身份。追踪库的历史身份保留为观测记录。常规首次升级无需这项身份维护。 diff --git a/skills.example/self-docs/references/docs-admin-mcp-servers.md b/skills.example/self-docs/references/docs-admin-mcp-servers.md index eb3700d0..a4a3a1d0 100644 --- a/skills.example/self-docs/references/docs-admin-mcp-servers.md +++ b/skills.example/self-docs/references/docs-admin-mcp-servers.md @@ -2,7 +2,105 @@ # MCP Server 接入指南 -QuickQuip 可以把外部 MCP server 提供的工具桥接给 AI,在对话的工具调用循环中按需使用。本文面向部署者,给出把现成 MCP server 接入 QuickQuip 的操作清单;MCP 协议概念与字段详解见 [docs/dev/mcp-tutorial.md](../dev/mcp-tutorial.md),server 开发不在本文范围。 +本文面向已经能用 Bot 正常对话的部署者,说明如何接入 MCP 工具服务并确认工具实际执行。MCP 服务提供搜索、读文件等能力;模型提出工具请求后,QuickQuip 联系服务执行,再把结果交给模型组织回复。概念与协议详解见 [MCP 教程](../dev/mcp-tutorial.md)。 + +第一次接入可完成下方本地 echo 练习;已有服务地址和凭据时,从[接入清单](#接入清单)开始。JSON 示例是完整导出配置中的字段片段,表单填写与片段合入位置在清单中说明,完整保存、应用流程见 [LLM 配置管理](llm-configuration.md)。 + +## 首次接入:本地 echo 练习 + +这个服务把传入文字原样回显,方便核对“Bot → 工具服务 → 模型回复”的过程。练习适用于同一台电脑上运行的源码 Bot 或 Windows 包;需要 Web Admin 管理权限、支持工具调用的模型,以及用于运行练习服务的标准 Python 3.11 或更高版本。Windows 使用标准 Python 的 `py` 启动器。 + +### 1. 准备练习目录 + +在 Bot 主机的个人工作目录中创建独立目录,放在项目仓库或 Windows 包目录之外。安装固定版本的[官方 MCP Python SDK](https://pypi.org/project/mcp/1.26.0/);下面命令在独立虚拟环境安装服务依赖。 + +Linux/macOS 的 Bash: + +```bash +mkdir mcp-echo-lab +cd mcp-echo-lab +python3 -m venv .venv +.venv/bin/python -m pip install "mcp==1.26.0" +``` + +Windows PowerShell: + +```powershell +New-Item -ItemType Directory mcp-echo-lab +Set-Location mcp-echo-lab +py -3 -m venv .venv +.\.venv\Scripts\python.exe -m pip install "mcp==1.26.0" +``` + +### 2. 创建并启动服务 + +用文本编辑器在刚创建的目录保存 `echo_server.py`,内容如下: + +```python +from mcp.server.fastmcp import FastMCP + +server = FastMCP( + "QuickQuip Echo", host="127.0.0.1", port=8017, + stateless_http=True, json_response=True, +) + + +@server.tool() +def echo(text: str) -> str: + """回显传入文本,供 MCP 接入测试。""" + print(f"echo 收到:{text}", flush=True) + return f"echo: {text}" + + +if __name__ == "__main__": + server.run(transport="streamable-http") +``` + +在练习目录中启动。Bash 执行: + +```bash +.venv/bin/python echo_server.py +``` + +PowerShell 执行: + +```powershell +.\.venv\Scripts\python.exe echo_server.py +``` + +终端出现 `Uvicorn running on http://127.0.0.1:8017` 后保持运行。MCP 端点为 `http://127.0.0.1:8017/mcp`:端点就是客户端提交 MCP 请求的完整地址,包含服务使用的路径。本例只监听本机回环地址,提供一个回显工具,无需账号或令牌。实现使用 SDK 1.26.0 的 Streamable HTTP,QuickQuip 使用缺省的 `legacy` 协商和 `2025-03-26` 版本即可;示例与官方 SDK 于 2026-10-14 核对。 + +容器中的 `127.0.0.1` 指向容器自身。本机练习使用直接运行在主机上的 Bot;容器 Bot 接入服务时,填写容器实际可达的地址:同一 Docker 网络中的服务可使用服务名,服务需监听该网络接口;宿主机服务需监听容器可达地址,并配置对应网关或路由。`host.docker.internal` 的解析取决于部署配置。生产接入可使用服务提供方已鉴权的 HTTPS 端点。 + +### 3. 在 Web Admin 添加服务 + +打开“LLM 配置 → 工具与技能”,开启“启用工具调用”。在“MCP 服务与状态”开启 MCP 总开关,点击“添加 MCP 服务”,填写: + +| 表单项 | 值 | +|---|---| +| 服务 ID | `echo_lab` | +| 传输方式 | `http` | +| 服务地址 | `http://127.0.0.1:8017/mcp` | +| 请求头 | 留空 | +| 协议协商 | `legacy` | +| 协议版本 | `2025-03-26` | +| 只装载这些工具 | `echo` | + +其余字段保留缺省值。若已有非空工具名单采用 `replace`,把 `mcp_echo_lab_echo` 加入该名单。点击顶部“保存草稿 → 应用到 Bot”,等待任务成功和 Bot 加载目标版本。刷新连接状态,`echo_lab` 应显示已连接、1 个工具;展开“已装载工具清单”可看到服务原始工具名 `echo`。Bot 使用的调用名称为 `mcp_echo_lab_echo`,由服务 ID 与原始工具名组成。 + +### 4. 调用并确认结果 + +在已开启 AI 的群或私聊发送下面的提问。这里使用默认 `/ai` 前缀;已修改前缀的会话使用实际设置: + +```text +/ai 请调用 mcp_echo_lab_echo,text 参数为 quickquip-mcp-162,并告诉我工具返回了什么。如果工具尚未加载,请先搜索 echo 工具再调用。 +``` + +服务终端出现 `echo 收到:quickquip-mcp-162`,表示服务已执行这次调用,并返回 `echo: quickquip-mcp-162`。机器人随后据此回复。确认时把服务终端的调用记录与机器人给出的工具结果对照。 + +若服务已连接而没有调用记录,检查当前模型是否支持工具调用,以及工具调用开关和工具名单;工具发现开启时,让模型先搜索并加载 echo。若显示连接失败,先确认服务终端保持运行、Bot 与服务在同一台主机、端口与 `/mcp` 路径一致,详细错误在“MCP 服务与状态”查看。 + +回显服务在本机执行;通过 Bot 测试会请求已配置的模型,按该模型渠道计费。后台“用量”显示模型费用,其他 MCP 服务自己的收费在对应服务账号核对。练习结束后,在 Web 停用或删除 `echo_lab` 并保存应用,再在服务终端按 Ctrl+C 停止服务。 ## Transport 怎么选 @@ -17,29 +115,42 @@ QuickQuip 可以把外部 MCP server 提供的工具桥接给 AI,在对话的 ## 接入清单 -1. **拿到连接信息**:远程 server 记下端点 URL 与鉴权凭证(如 Bearer token);本地 server 记下启动命令或镜像名,以及所需环境变量。 -2. **写配置**:在 `config/llm.toml` 打开总开关并声明 server 条目,可参照 `config/llm.toml.example` 的 `[[mcp.servers]]` 注释段: - - ```toml - [mcp] - enabled = true - - [[mcp.servers]] - id = "my_server" - transport = "http" - timeout_seconds = 30 - url = "https://mcp.example.com/mcp" +先确认基础 LLM 对话可用,并在“LLM 配置 → 工具与技能”开启 `settings.runtime.tool_calling_enabled`。新建配置库的工具调用缺省关闭,Bot 通过此开关向模型提供已连接的 MCP 工具;若工具名单非空且采用 `settings.tools.enabled_mode = "replace"`,显式允许需要的 MCP 工具。以下配置与工具开关一并保存草稿并应用。 + +1. **拿到连接信息**:从服务提供方的接入说明取得 MCP 端点 URL、传输方式、协议版本和鉴权方法。端点包含服务使用的路径;凭据由该工具服务签发,与模型渠道的 API key 分别配置。先选择一个读类工具,按其所需权限申请凭据,并核对服务方计费说明。本地 server 还需准备启动命令或镜像,以及所需环境变量。 +2. **写配置**:在“工具与技能 → MCP 服务与状态”打开 MCP 总开关,点击“添加 MCP 服务”,按示例填写服务 ID、传输方式和服务地址。以下为导出 JSON 内的字段对照,`mcp.example.com` 需替换为实际 MCP 端点: + + ```json + { + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "my_server", + "transport": "http", + "timeout_seconds": 30, + "url": "https://mcp.example.com/mcp" + } + ] + } + } + } ``` - `id` 在全部 server 间唯一,重复条目会被跳过并记录告警;`transport` 缺省为 `stdio`,`timeout_seconds` 缺省为 30,单个 server 的 `enabled` 缺省为 `true`,设为 `false` 可临时停用而保留配置。 -3. **凭证走环境变量**:server 条目内的字符串值(`url`、`headers`、`env`、`mounts` 等)支持 `${ENV_VAR}` 与 `${ENV_VAR:-default}` 展开——未设置的 `${ENV_VAR}` 展开为空串,`${ENV_VAR:-default}` 展开为默认值。凭证一律写在仓库根 `.env`,禁止明文写进 toml: + 使用 JSON 导入时,先导出完整配置,将示例服务对象追加到 `settings.mcp.servers`,设置 `settings.mcp.enabled` 并保留其他字段与已有服务;预览完整文件差异后再导入。`id` 在全部 server 间唯一,重复条目会被保存校验拒绝;`transport` 缺省为 `stdio`,`timeout_seconds` 缺省为 30,单个 server 的 `enabled` 缺省为 `true`,设为 `false` 可临时停用而保留配置。 +3. **凭证走环境变量**:server 条目内的普通字符串支持 `${ENV_VAR}` 与 `${ENV_VAR:-default}` 展开;认证字段只保存 `${ENV_VAR}` 引用——未设置的 `${ENV_VAR}` 展开为空串,`${ENV_VAR:-default}` 展开为默认值。凭证一律写在仓库根 `.env`,配置数据库和 JSON 中仅保存变量引用: - ```toml - env = { GITHUB_TOOLSETS = "${GITHUB_TOOLSETS:-context,repos,issues}" } + ```json + { + "env": { + "GITHUB_TOOLSETS": "${GITHUB_TOOLSETS:-context,repos,issues}" + } + } ``` 上例在 `.env` 未定义 `GITHUB_TOOLSETS` 时取默认值,定义后取环境变量的值。 -4. **生效**:重启 bot,或群内执行 `/llm reload`(管理员)重载 `config/llm.toml` 并重连全部 MCP server;`/llm mcp reload`(管理员)只重连 MCP,且对 docker transport 强制拉取最新镜像。`.env` 变量的新增与修改需要重启 bot 进程。 +4. **生效**:保存草稿后单独应用,查询配置任务状态与 Bot 实际加载版本。MCP 配置发生变化时创建新的连接资源;价格等无关修改不会重连。`/llm mcp reload`(管理员)用于手动重连,docker transport 会拉取镜像。`.env` 修改需要重启 Bot;重启加载已发布配置。 5. **验证装载**:群内 `/llm mcp status` 查看,输出形如: ```text @@ -51,30 +162,39 @@ QuickQuip 可以把外部 MCP server 提供的工具桥接给 AI,在对话的 - fetch [sse] ERROR tools=0 error=连接超时 ``` - `ON` 表示已连接,`OFF` 表示该 server 被停用,`ERROR` 表示装载失败;聊天面的 `error=` 只显示失败类别,脱敏后的具体错误文本在 Web Admin「MCP」页查看。配置了 `negotiation` 的 http server 会在 transport 后附带协议纪元标记(如 `[http/auto/modern]`),表示协商模式与实际协商结果。 -6. **健康检查与用量观察**:`/llm probe`(管理员)并发探活全部 LLM provider,确认模型侧链路可用(每次调用按 provider 计费)。MCP 工具在 LLM 工具循环内执行,相关用量与成本在 Web Admin「用量」页按 provider / 模型 / 功能 / 群 / 人格维度查看。 - -## 最小 http 示例 - -一个带 Bearer token 的远程 server,token 全部走环境变量引用: - -```toml -[mcp] -enabled = true - -[[mcp.servers]] -id = "prts_wiki" -transport = "http" -timeout_seconds = 30 -url = "https://mcp.example.com/mcp" -headers = { Authorization = "Bearer ${MCP_PRTS_WIKI_TOKEN}" } + `ON` 表示已连接,`OFF` 表示该 server 被停用,`ERROR` 表示装载失败;聊天面的 `error=` 只显示失败类别,脱敏后的具体错误文本在“LLM 配置 → 工具与技能 → MCP 服务与状态”查看。配置了 `negotiation` 的 http server 会在 transport 后附带协议纪元标记(如 `[http/auto/modern]`),表示协商模式与实际协商结果。 +6. **调用与费用确认**:在已开启 AI 的会话中,请机器人调用选定的读类工具,给出明确参数;将服务调用日志与返回内容对照,核对预期结果,本地练习的方法见上文。模型调用用量和估算费用在 Web Admin“用量”页查看,工具服务的收费在该服务账号核对。需要巡检模型渠道时,管理员可另行执行 `/llm probe`;它会请求全部启用渠道的默认模型并可能产生费用。 + +## 需要鉴权的 HTTP 配置模板 + +服务提供方要求 Bearer token 时,按其说明取得令牌,将下方占位 URL 换成实际端点。令牌保存在根 `.env`,请求头字段填写变量引用;其他鉴权方法按提供方指定的 Header 名称填写。 + +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "prts_wiki", + "transport": "http", + "timeout_seconds": 30, + "url": "https://mcp.example.com/mcp", + "headers": { + "Authorization": "Bearer ${MCP_PRTS_WIKI_TOKEN}" + } + } + ] + } + } +} ``` 对应 `.env` 条目(体例同 `.env.example`): -```bash -# prts_wiki MCP 的鉴权 token,见 config/llm.toml.example。 -MCP_PRTS_WIKI_TOKEN=<你的 token> +```env +# prts_wiki MCP 的鉴权 token,对应上面的服务配置。 +MCP_PRTS_WIKI_TOKEN=your_token_here ``` ## 常用进阶配置 @@ -88,19 +208,20 @@ MCP_PRTS_WIKI_TOKEN=<你的 token> | `negotiation` | 协议协商模式,仅 `http` transport 生效:`legacy`(默认)/ `auto` / `modern`;`auto` / `modern` 需同时配置 `supported_protocol_versions` | | `image` / `mounts` | docker transport 的镜像与卷挂载,格式 `host:container` 或 `host:container:ro`,值支持 `${ENV_VAR}` 展开 | -字段全集与默认值见 [configuration.md](configuration.md) 的 `[mcp]` 段,语义详解见 [mcp-tutorial.md](../dev/mcp-tutorial.md)。接入 GitHub MCP 这类大工具集时,建议先用 `include_tools` 收窄到读类工具,再交给 `tool_search` / `tool_list` 做按需发现加载。 +字段全集与默认值见 [configuration.md](configuration.md) 的 `settings.mcp` 段,语义详解见 [mcp-tutorial.md](../dev/mcp-tutorial.md)。接入 GitHub MCP 这类大工具集时,建议先用 `include_tools` 收窄到读类工具,再交给 `tool_search` / `tool_list` 做按需发现加载。 ## 排障 -- **装载失败**:先看 `/llm mcp status` 的 `error=` 类别(配置错误 / 认证失败 / 连接超时 / 传输错误等);显示「总开关:OFF」时检查 `[mcp] enabled = true` 是否已设。脱敏后的具体错误文本与手动重连入口在 Web Admin「MCP」页和「诊断」页。启动时的瞬时连接失败会自动重试(最多 3 次、间隔 2 秒),认证与配置类错误直接失败。 -- **别名冲突**:不同 server 生成相同工具名时按 fail-closed 处理,冲突工具全部不注册,status 标为配置错误;用 `tool_prefix` 区分。 -- **server 已连接但工具没出现**:检查该 server 的 `include_tools` / `exclude_tools` 过滤,以及 `[tools]` 的 `enabled` / `enabled_mode` 是否把 MCP 工具从工具面过滤掉。 -- **stale session(http legacy)**:会话过期后 `tools/list` 等只读请求会在有界次数内(最多 2 次)自动重连;`tools/call` 不自动重放,当次调用失败,下一次调用走新会话。 +- **装载失败**:先看 `/llm mcp status` 的 `error=` 类别(配置错误 / 认证失败 / 连接超时 / 传输错误等);显示“总开关:OFF”时检查 `settings.mcp.enabled = true` 是否已设。脱敏后的具体错误文本与手动重连入口在 “LLM 配置 → 工具与技能 → MCP 服务与状态”和“诊断”页。启动时的瞬时连接失败会自动重试(最多 3 次、间隔 2 秒),认证与配置类错误直接失败。 +- **别名冲突**:不同 server 生成相同工具名时冲突工具全部不注册,status 标为配置错误;用 `tool_prefix` 区分。 +- **server 已连接但工具没出现**:检查该 server 的 `include_tools` / `exclude_tools` 过滤,以及 `settings.tools` 的 `enabled` / `enabled_mode` 是否把 MCP 工具从工具面过滤掉。 +- **工具已装载但模型没有调用**:确认当前模型支持工具调用,全局工具调用开关已应用;给出具体工具名称、参数和任务。工具发现启用时,模型先通过 `tool_search` 或 `tool_list` 加载工具,再发起调用。 +- **stale session(http legacy)**:会话过期后 `tools/list` 等只读请求会最多自动重连 2 次;`tools/call` 不自动重放,当次调用失败,下一次调用走新会话。 - **工具结果大小边界**:resource 文本超过 60,000 code point 截断并附固定标记;图片单张上限 5 MiB、每个工具结果最多交付 5 张(仅 PNG / JPEG / GIF / WebP)。 - 深入排查见 [mcp-integration.md](../dev/mcp-integration.md)。 ## 延伸阅读 - [docs/dev/mcp-tutorial.md](../dev/mcp-tutorial.md) — MCP 概念教程 -- [configuration.md](configuration.md) — `config/llm.toml` 字段全集 +- [configuration.md](configuration.md) — `data/llm_config.db` 字段全集 - [docs/dev/mcp-integration.md](../dev/mcp-integration.md) — MCP 集成设计与决策 diff --git a/skills.example/self-docs/references/docs-admin-migration-napcat-to-llbot.md b/skills.example/self-docs/references/docs-admin-migration-napcat-to-llbot.md index 283153e1..2fc3f9bc 100644 --- a/skills.example/self-docs/references/docs-admin-migration-napcat-to-llbot.md +++ b/skills.example/self-docs/references/docs-admin-migration-napcat-to-llbot.md @@ -2,7 +2,7 @@ # NapCat → LLBot 迁移指南 -> **历史迁移记录**:本文档记录 QuickQuip 2026-05 从 NapCat 迁移到 LLBot 时的决策背景、迁移步骤与回退思路,保留当时的版本与环境前提。它不承担现行运维职责——当前适配器状态与选择见 [onebot-adapters.md](onebot-adapters.md),现行部署流程见 [deployment.md](deployment.md)。 +> **历史迁移记录**:本文档记录 QuickQuip 2026-05 从 NapCat 迁移到 LLBot 时的决策背景、迁移步骤与回退思路,保留当时的版本与环境前提。当前适配器状态与选择见 [onebot-adapters.md](onebot-adapters.md),现行部署流程见 [deployment.md](deployment.md)。 QuickQuip 设计之初即以 NapCat(Docker 镜像 `mlikiowa/napcat-docker`)作为推荐的 OneBot V11 QQ 协议适配器。截至 2026 年 5 月中下旬,NapCat 遭遇腾讯高强度风控打击,社区和我们的生产环境均反复出现以下问题: @@ -29,7 +29,7 @@ QuickQuip 设计之初即以 NapCat(Docker 镜像 `mlikiowa/napcat-docker`) ## 迁移步骤 -以下步骤基于 `docker-compose.example.yml` 的结构。如果你的部署使用了自定义 compose 文件,请对应调整。 +以下步骤面向已有 NapCat 部署者,基于当时 `docker-compose.example.yml` 的结构,示例统一使用反向 WS。命令在已有部署的 `docker-compose.yml` 所在目录执行;自定义服务名、文件路径和连接配置按原部署调整。先保留原 Compose 与 `.env` 的副本,供回退时恢复。 ### 1. 在 `docker-compose.yml` 中新增 LLBot 服务 @@ -46,7 +46,7 @@ services: - QUICK_LOGIN_QQ=${QQ_ACCOUNT:?请设置 QQ 号} - TZ=Asia/Shanghai ports: - - "127.0.0.1:3001:3001" # OneBot WebSocket(正向,备用) + - "127.0.0.1:3001:3001" # 正向 WS 可选端口,本示例不启用 - "127.0.0.1:3080:3080" # WebUI(扫码登录 / 配置管理) volumes: - ./llbot-qq:/root/.config/QQ # 登录态持久化(关键,切勿丢失) @@ -58,7 +58,22 @@ services: ### 2. 配置 OneBot 反向 WS -LLBot 的配置为 JSON 格式,位于 `llbot-data/default_config.json`。最小配置(启用反向 WS): +先启动 LLBot,查看日志中的二维码或登录链接: + +```bash +docker compose up -d llbot +docker compose logs -f llbot +``` + +WebUI 端口绑定服务器本机。需要浏览器扫码和配置时,在自己的电脑上另开终端,将 `user@server` 替换为服务器账号与地址: + +```bash +ssh -N -L 3080:127.0.0.1:3080 user@server +``` + +保持隧道运行,在本地浏览器打开 `http://127.0.0.1:3080`,完成扫码登录后启用 OneBot V11 的反向 WS(`ws-reverse`),连接地址为 `ws://quickquip:8080/onebot/v11/ws/`。token 填现有根目录 `.env` 中的 `ONEBOT_ACCESS_TOKEN`;该值为空时,先设置一个非空 token,两侧保持同值。 + +LLBot 配置为 JSON 格式。下方是当时 `llbot-data/default_config.json` 的最小结构示例,`token` 替换为上述值: ```json { @@ -71,7 +86,7 @@ LLBot 的配置为 JSON 格式,位于 `llbot-data/default_config.json`。最 "enable": true, "url": "ws://quickquip:8080/onebot/v11/ws/", "heartInterval": 60000, - "token": "", + "token": "替换为ONEBOT_ACCESS_TOKEN的值", "messageFormat": "array" } ] @@ -81,11 +96,11 @@ LLBot 的配置为 JSON 格式,位于 `llbot-data/default_config.json`。最 } ``` -> 注意:容器首次启动时入口脚本可能用内置默认配置覆盖此文件。建议先启动容器完成首次登录,再通过 WebUI(`http://<服务器IP>:3080`)配置反向 WS,或使用 `docker exec` 修改 `data/config_.json`。 +容器首次启动时入口脚本可能用内置默认配置覆盖 `default_config.json`。上述顺序先完成首次启动和登录,再通过 WebUI 修改当前账号配置;当时也可在容器内编辑 `data/config_.json`。 ### 3. 更新 QuickQuip 服务 -在 compose 中将 QuickQuip 的 `depends_on` 和 `ONEBOT_WS_URLS` 更新为指向 LLBot 正向 WS: +将 QuickQuip 的协议端依赖从 NapCat 改为 LLBot,保留其他依赖与配置。下方是合入现有 `quickquip` 服务的片段:清空正向 WS 目标,让 LLBot 按 §2 主动连接,并把根 `.env` 的 token 传给 QuickQuip。 ```yaml quickquip: @@ -93,24 +108,20 @@ quickquip: - llbot environment: DRIVER: "${DRIVER:-~fastapi+~websockets}" - ONEBOT_WS_URLS: '${ONEBOT_WS_URLS:-["ws://llbot:3001/"]}' + ONEBOT_WS_URLS: '[]' + ONEBOT_ACCESS_TOKEN: "${ONEBOT_ACCESS_TOKEN:?请设置 OneBot token}" ``` -### 4. 启动并扫码登录 +### 4. 应用配置并验证连接 -```bash -docker compose up -d llbot -# 查看日志获取二维码或访问 WebUI -docker compose logs -f llbot -# 或者浏览器打开 http://<服务器IP>:3080 -``` - -扫码完成后重启 QuickQuip 建立新连接: +按更新后的 Compose 重建 QuickQuip,使新依赖与环境变量生效: ```bash -docker compose restart quickquip +docker compose up -d quickquip ``` +单纯 `restart` 重启原容器;修改 Compose 或环境变量后使用 `up -d` 应用配置。 + 验证连接成功: ```bash @@ -130,9 +141,9 @@ NapCat 的登录态和数据卷(`napcat-data/`)建议在确认稳定前保 ## OneBot WS 模式说明 -LLBot 同时支持正向和反向 WebSocket。`docker-compose.example.yml` 默认使用**正向 WS**:QuickQuip 通过 `ONEBOT_WS_URLS='["ws://llbot:3001/"]'` 连接 LLBot 的 3001 端口。此模式需要 `DRIVER` 包含 `~websockets`。 +LLBot 同时支持正向和反向 WebSocket。本文步骤采用反向 WS:LLBot 主动连接 QuickQuip,QuickQuip 的 `ONEBOT_WS_URLS` 为空数组。 -如果你更希望保留 NapCat 时期常见的反向 WS 结构,也可以在 LLBot WebUI 中配置 `ws-reverse`,让 LLBot 连接 QuickQuip 的 `/onebot/v11/ws/` 端点。 +当时的 `docker-compose.example.yml` 默认采用正向 WS:QuickQuip 通过 `ONEBOT_WS_URLS='["ws://llbot:3001/"]'` 连接 LLBot 的 3001 端口。选择正向模式时,需在 LLBot WebUI 启用正向 WS 服务端,两侧 token 同值,QuickQuip 的 `DRIVER` 包含 `~websockets`。现行新部署步骤见[部署指南](deployment.md)。 ## 已知差异 @@ -146,22 +157,17 @@ LLBot 同时支持正向和反向 WebSocket。`docker-compose.example.yml` 默 ## 回退步骤 -如 LLBot 出现严重问题: +如 LLBot 出现严重问题,先停止它,并恢复迁移前保存的 Compose 与 `.env` 配置,包括协议端依赖、WS 目标和 token: ```bash # 停止 LLBot docker compose stop llbot -# 恢复 ONEBOT_WS_URLS -# 将 .env 或 compose 中的 ws://llbot:3001/ 改回 ws://napcat:6099 - -# 恢复 depends_on(如有改动) - -# 重启 QuickQuip -docker compose restart quickquip +# 恢复迁移前的 Compose 与 .env 后,重新启动 NapCat +docker compose up -d napcat -# 启动 NapCat -docker compose start napcat +# 应用恢复后的 QuickQuip 配置 +docker compose up -d quickquip ``` ## 参考 diff --git a/skills.example/self-docs/references/docs-admin-onebot-adapters.md b/skills.example/self-docs/references/docs-admin-onebot-adapters.md index 101200e1..275f4db0 100644 --- a/skills.example/self-docs/references/docs-admin-onebot-adapters.md +++ b/skills.example/self-docs/references/docs-admin-onebot-adapters.md @@ -2,13 +2,13 @@ # OneBot 适配器状态与选择 -QuickQuip 应用层只依赖 NoneBot2 + OneBot V11 契约,不绑定任何具体协议端实现。本文档是 OneBot 协议端(下称"适配器")的状态入口:QuickQuip 实际依赖的协议边界、各候选适配器的部署 profile 与验证状态、以及更换适配器前的统一验证清单。 +本文面向选择或更换 QQ 协议端的部署者。QuickQuip 通过 NoneBot2 与 OneBot V11 连接协议端;下文记录所需接口、各适配器的部署方案与本项目验证状态,以及迁移前检查清单。 更换适配器时,QuickQuip 业务代码无需改动——只需替换协议端部署、调整 `.env` 中的 OneBot 连接配置。历史上 NapCat → LLBot 的迁移即按此方式进行(见 [migration-napcat-to-llbot.md](migration-napcat-to-llbot.md))。 ## QuickQuip 的 OneBot V11 边界 -任何 OneBot V11 实现只要满足以下边界,理论上都可对接 QuickQuip;实际可用性以逐项验证为准(见文末验证清单)。 +部署候选须提供以下连接、事件、接口和消息段,再按文末清单检查实际收发与恢复能力。 **连接拓扑** @@ -60,7 +60,7 @@ QuickQuip 应用层只依赖 NoneBot2 + OneBot V11 契约,不绑定任何具 | WebUI | `3080`(扫码登录、网络方式配置) | | 卷 | `llbot-qq/`(登录态,切勿丢失)、`llbot-data/`(配置与运行时数据) | | 快速登录 | `QUICK_LOGIN_QQ` 环境变量;有时效性,失效需重新扫码 | -| 已验证能力 | v1.12.2 Windows/Linux Docker 验收:群/私聊、图片、语音 ASR、合并转发、撤回事件全链路真实消息验证;v1.16.1-dev 验收(2026-10):群聊 LLM 回复的出站 `reply` 段引用锚定(客户端正常渲染引用预览)与「@名字」转真实 `at` 段提醒 | +| 已验证能力 | v1.12.2 Windows/Linux Docker 验收:群/私聊、图片、语音 ASR、合并转发、撤回事件全链路真实消息验证;v1.16.1-dev 验收(2026-10):群聊 LLM 回复的出站 `reply` 段引用锚定(客户端正常渲染引用预览)与“@名字”转真实 `at` 段提醒 | **版本 pin 说明**:上游 `latest` 自 2026-08 起漂移到 pmhq 8.x——启动即要求在 auth.luckylillia.com 注册获取 `auth_token`(部分账号需人工审核),新部署会直接卡死在授权提示上。因此模板固定在 7.3.2。如需更换版本:NTQQ 强制升级导致旧构建无法登录时,到上游 Docker Hub 挑选新的 `vX.Y.Z-…` tag 更新模板;升级前先确认目标版本的授权要求。 @@ -84,7 +84,7 @@ LLBot 8.x 未经过本项目生产验证,不在当前候选池。 **复评门槛**(全部满足才重新进入评估): -- 当前正式 Docker artifact 与目标 QQ 版本(旧版组合的稳定样本不能替代当前版本证据;未合入的反检测 PR 不能视为修复证明)。 +- 固定当前正式 Docker 发行物与目标 QQ 版本,在该组合上收集运行记录。 - 隔离测试账号,至少 72 小时(最好 7 天)连续运行。 - 覆盖:踢下线、静默断联、重新登录、二维码刷新、手机端并行登录、图片/语音/合并转发、撤回事件。 @@ -102,7 +102,7 @@ LLBot 8.x 未经过本项目生产验证,不在当前候选池。 **前置条件**(进入生产前必须完成): -1. **部署授权澄清**:其 TS 层为源码可见非商业许可(非 OSI),EULA 对"并入第三方 Docker 镜像 / 自动化脚本部署"要求书面授权——需与作者书面确认 compose 自动化部署的边界。 +1. **部署授权澄清**:其 TS 层为源码可见非商业许可(非 OSI),[EULA 1.1 第 5.4 条](https://github.com/SnowLuma/SnowLuma/blob/main/EULA.md)要求专有组件并入第三方 Docker 镜像或通过自动化脚本部署前取得书面授权;部署前需确认所用方式的授权。条款核对日期为 2026-10-14。 2. **运行时出流量审计**:核心 native hook 组件闭源分发,需以容器出向网络目标清单收口隐私边界。 3. **QQ 版本跟进实测**:QQ Linux 3.2.32 存在"hook 连接成功但登录身份不上报"的公开报告(issue 已被自动关闭,未确认修复);需 pin 一个版本观察完整的 NTQQ 升级适配周期。 4. **兼容实测三件套**:`send_group_forward_msg` 自定义 `uin`/`name` 节点、`get_record` 返回本地路径形态、入站 `image` 段 `data.url` 直连性。 @@ -125,10 +125,10 @@ LLBot 8.x 未经过本项目生产验证,不在当前候选池。 11. 运行期间的出向网络目标与隐私边界。 12. 账号风控、设备风险、静默断联和不可恢复登录故障。 -**证据分级**:静态源码 / 发行物 / 运行时 / 真实消费者 / 长周期稳定性五层,逐层收集。单次成功、绿色 CI 或上游 README 声明不能单独构成生产准入依据。 +**迁移记录**:按源码、发行物、运行时、真实消息和长周期稳定性分别记录检查结果,附具体版本、操作与日志。 ## profile 状态更新规则 -- 状态变更(如候选 → 生产基线、NO-GO → 复评中)须附对应层级的证据链接或验收记录,不接受口头或上游宣传依据。 +- 状态变更(如候选 → 生产基线、NO-GO → 复评中)须附对应检查的证据链接或验收记录。 - 每次适配器相关 release 验收后,更新本页的已验证能力与版本 pin。 - 历史迁移记录([migration-napcat-to-llbot.md](migration-napcat-to-llbot.md))只记录当时的迁移决策与环境,不承担现行运维职责;现行部署细节以本页 profile 与 [deployment.md](deployment.md) 为准。 diff --git a/skills.example/self-docs/references/docs-admin-record-identities.md b/skills.example/self-docs/references/docs-admin-record-identities.md index bdfd91b6..80e2fe85 100644 --- a/skills.example/self-docs/references/docs-admin-record-identities.md +++ b/skills.example/self-docs/references/docs-admin-record-identities.md @@ -32,13 +32,13 @@ python scripts/backfill_record_identities.py --database memories --group 123456 每个数据库写入前使用 SQLite backup 生成同目录下带 UTC 时间戳的 `.identities-*.bak` 文件,并打印备份路径。每批默认 200 条,可用 `--batch-size` 调整。写入前在事务内核对源正文和片段值,遇到并发编辑或删除时跳过。已有片段保留,仅补充缺失引用索引;重复执行可继续完成剩余记录。任意失败返回非零退出码。 -旧数据中合法 CQ 示例可能按历史提及展示;请通过预览和原文查看核对。工具仅依据保存的 QQ 转换,不调用模型推断身份。真实生产数据应先只读统计,再选择维护窗口执行需要的回填。 +旧数据中合法 CQ 示例可能按历史提及展示;请通过预览和原文查看核对。身份转换依据已保存的 QQ 号。真实生产数据应先只读统计,再选择维护窗口执行需要的回填。 ## 恢复与验收 恢复时停止 Bot 和 Web 的数据库写入,保留当前数据库及其 WAL/SHM 文件,使用选定备份的 SQLite backup 恢复目标数据库,然后重新启动服务。备份包含回填前的整个数据库;恢复范围包含同库其他业务表,应按事故窗口核对新增数据。 -发布前在真实群聊核对: +回填后可在实际群聊检查展示与检索: 1. 对已登记成员发送带艾特的 `/remember`,在 `/memories` 和后台查看标准身份,原文可查。 2. 引用带 `qq` 和 `name` 的消息收藏语录,确认未登记成员使用可用名字,原文中的 `/remember` 保留。 diff --git a/skills.example/self-docs/references/docs-admin-sensitive-filter.md b/skills.example/self-docs/references/docs-admin-sensitive-filter.md index 79d84554..b7f1ae71 100644 --- a/skills.example/self-docs/references/docs-admin-sensitive-filter.md +++ b/skills.example/self-docs/references/docs-admin-sensitive-filter.md @@ -1,163 +1,90 @@ -# 敏感词过滤器(sensitive_filter) +# 敏感词过滤部署与维护 -QuickQuip 在 LLM 和多模态相关文本的流量边界接入敏感词过滤,目的是: - -1. **防止账号被封**:触发 LLM 提供商网关层审核(如 DeepSeek 的 `Content Exists Risk`、阿里云的 `Content security warning`)多次后,API Key 可能被封禁 -2. **防止群被炸**:模型生成的违规内容如果被 bot 发到群里,群本身和发言群员(即使是 bot)都可能被处罚 -3. **减少历史污染**:旧消息中的敏感内容如果继续作为 context 注入下次请求,会持续触发问题 - -过滤器位于 `src/quickquip/common/sensitive_filter.py`,由词表配置文件 `config/sensitive_words.toml` 驱动。**该词表文件被 gitignored,必须由部署者自行填充。** - -## 工作原理 - -### 两级匹配 - -- **block 级**:命中即中断 - - 输入侧命中:直接返回固定回复,**不调用 LLM**(既防止账号被审核标记,也节省 token) - - 输出侧命中:替换为兜底回复,**不写入历史**(防止污染下一轮 context) - - 历史侧命中:用 `[内容已屏蔽]` 替换,仅影响当次注入到 LLM 的 messages,**不修改数据库存储的原文** - -带工具调用的历史会话按完整 Loop 应用当前词表,检查触发消息、回复正文、工具参数与结果,以及原生块中的可读文本。命中 block 或包含已被输出过滤替换的 Turn 时,本次请求使用清洗后的文本档案,保留正文和工具状态汇总,省略工具详情与原生签名块。预算精简继续使用该档案;未命中的会话沿用原有重放方式。 - -- **soft 级**:仅记录日志,不阻断 - - 用于监控边缘词、推广话术等。日志累积一段时间后可以人工评估是否提升为 block - -### 算法 - -纯 Python 实现的 **Aho-Corasick 自动机**,对几千词级别的词表,单次扫描 < 1ms,无需引入 C 扩展依赖。 - -匹配前会做轻量归一化: -- `casefold()` 大小写折叠 -- 移除零宽字符(U+200B/200C/200D/FEFF/00AD) -- 移除 ASCII 空白(让 “六 四” 也能命中 “六四”) - -**未实现**拼音/同形异构字归一化——那是无底洞,且误报会爆炸。这层定位是**绊线**,不是对抗“研究型对手”的纵深防御。 - -### 日志 - -命中只记录类别和 SHA-256 前 12 位哈希,**不记录原文**。这是因为日志文件本身可能成为合规风险。日志条目示例: - -``` -WARNING quickquip.common.sensitive_filter sensitive_filter[input] blocked scope=12345 hits=fraud:a1b2c3d4e5f6,gambling:9876fedcba01 -``` - -要查具体词,需要本地对照 `config/sensitive_words.toml` 自行计算哈希。 +本文面向部署者,说明如何维护 `config/sensitive_words.toml`、检查过滤器状态和处理误拦截。过滤器按部署词表检查聊天与多模态链路中的文本,减少敏感内容发送给模型、群聊或后续对话的机会。 ## 部署步骤 -### 1. 复制模板 +### 1. 准备词表 + +在项目根目录执行: ```bash cp config/sensitive_words.toml.example config/sensitive_words.toml ``` -模板中只包含通用反诈/反垃圾词(杀猪盘、跑分平台、伪造证件等),**无政治、宗教、暴力、色情类**——这些维度需要部署者根据所在司法辖区自行填充。 +模板提供通用反诈、反垃圾词。实际词表由部署者按使用环境维护,文件已被 Git 忽略。类别名称由部署者自定,每类使用 `words` 数组: + +```toml +[block.fraud] +words = ["杀猪盘", "跑分平台"] -### 2. 填充 17 类高风险场景 +[soft.promotion] +words = ["推广返利"] +``` -国内大模型备案要求覆盖 17 类高风险内容(见《生成式人工智能服务安全基本要求》)。建议按下表骨架组织: +两级词表分别用于拦截和观察: -| 类别 | 匹配模式 | 起步示例方向 | +| 级别 | 行为 | 适合的词条 | |---|---|---| -| `political_leaders` | 上下文敏感(需搭配攻击性动词) | 现任领导人姓名 + 倒台/暗杀/讽刺等 | -| `political_events` | 绝对词 | 历史敏感事件名称及其变体 | -| `territorial` | 绝对词 | 领土主权类表述 | -| `ethnic_religion` | 绝对词 | 民族宗教(敏感方向) | -| `banned_organizations` | 绝对词 | 被禁组织、邪教名称 | -| `separatism` | 绝对词 | “X 独”模板很稳 | -| `violence_terror` | 绝对词 | 暴恐组织名 + 招募/加入 | -| `obscenity_minor` | 绝对词 | 涉未成年人色情 | -| `obscenity_explicit` | 绝对词 | 露骨色情词 | -| `drugs` | 绝对词 + 价格/出售 | 毒品名称 + 交易动词 | -| `weapons` | 绝对词 | 自制武器、改装枪等 | -| `fraud` | 绝对词 | 诈骗教程类 | -| `gambling` | 绝对词 | 赌博平台/教程 | -| `hate_speech` | 上下文敏感 | 仇恨言论(建议交给 LLM 后处理而非词表) | -| `suicide_promotion` | 绝对词 | 自杀教程/诱导 | -| `private_info_doxxing` | 绝对词 | 人肉搜索类 | -| `discrimination` | 上下文敏感 | 歧视言论 | +| `block` | 阻断请求或替换命中文本,具体处理见下方覆盖表 | 在部署场景中明确需要拦截的词语 | +| `soft` | 记录命中日志,请求照常执行 | 容易误伤、需要观察的词语 | -**起步建议**(最小集,约 50-80 词): -1. 先填 `banned_organizations`、`separatism`、`violence_terror`——这三类几乎全是绝对词,零误伤 -2. 再补 `weapons`、`fraud`、`gambling`——商业判定明确 -3. 最后做 `political_leaders`、`political_events`——最容易误伤,优先用上下文匹配 -4. `hate_speech`、`discrimination`——建议交给 LLM 后处理,词表无法覆盖语境 +### 2. 筛选并试用词条 -### 3. 词表来源 +可从公开词表、测试群消息和模型拒答记录中筛选词条。先用小词表观察命中日志,再根据误拦截情况调整级别;引用第三方词表时核对其许可。 -不要硬抄完整商业词表(误伤率极高)。建议综合: +类别用于组织词条和日志,所有类别采用同一种字面匹配。匹配前会折叠大小写,并移除零宽字符和 ASCII 空白,例如词条“推广返利”也能命中“推广 返利”。拼音、谐音和同形字需要作为独立词条添加。依赖语境判断的词语适合先放入 `soft`,观察后再决定是否拦截。 -- **GitHub 开源词表**:起步快,但需人工筛选 -- **观察 LLM 拒答记录**:你的 LLM 提供商每次返回 `Content Exists Risk` / 安全警告,都是免费的标注数据 -- **阿里云/腾讯云内容安全 API**:覆盖更全但增加延迟和成本,对群聊 bot 而言 overkill -- **测试群跑半个月,记录所有模型主动拒答**——这是质量最高的源 +### 3. 重载并检查状态 -### 4. 重载与状态查看 +更新词表后,在有管理权限的群聊执行 `/llm reload`,或重启 Bot。`/llm reload` 同时重载已发布 LLM 配置及相关资产,并对当前模型发起真实探测请求,可能产生费用。 -修改 `config/sensitive_words.toml` 后,调用 `reload_filter()` 即可热更新(不需要重启 bot)。当前没有独立的群内重载命令;在服务器本地更新词表后,可执行 `/llm reload` 或重启 bot。 +群内 `/llm health verbose` 展示 `sensitive_filter` 健康项。Web Admin 的受保护只读接口 `GET /ops/api/sensitive-filter/status` 返回文件是否存在、是否已加载及 block/soft/total 数量。词表内容在服务器本地维护,后台只提供状态。 -Web Admin 提供只读状态接口 `GET /ops/api/sensitive-filter/status`,返回配置文件是否存在、是否已加载以及 block/soft/total 计数。它不会返回词表内容、分类明细或文件路径。群内 `/llm health verbose` 也会展示 `sensitive_filter` 健康项,但不会回显词表路径。 +## 过滤后的行为 -## 审查边界 +| 环节 | 命中 `block` 时的结果 | +|---|---| +| 普通 LLM 输入 | 返回固定提示,本轮主模型请求中止 | +| 普通 LLM 输出 | 用固定提示替换正文,发送与写入对话历史的都是替换后的文本 | +| 历史消息 | 本次模型输入用 `[内容已屏蔽]` 替换命中片段,数据库原文保留 | +| 工具参数 | 拒绝本次工具执行,模型收到错误结果 | +| 工具结果 | 命中数不超过 5 个且结果至少 200 字时替换命中片段;其余情况整段替换,均向模型标记结果不完整 | +| Skill 描述 | 对应 Skill 从模型清单剔除,并记录告警 | +| Skill 激活正文 | 拒绝本次激活 | +| Skill 资源、检索结果和脚本输出 | 按工具结果规则处理 | +| `/draw`、`/tts` 输入 | 中止命令,相关提示词或引用文本不会提交给生成服务 | +| 音乐生成输入 | 中止命令,提示词、标题和歌词不会提交给生成服务 | +| 歌词输出 | 用固定提示替换,歌词不会发送或继续提交给音乐服务 | +| ASR 转写 | 转写后按普通 LLM 输入规则扫描;原始音频已发送给 ASR 服务 | +| 图片转述 | 主模型输入前命中时中止主模型请求;视觉模型已读取原图。工具结果图片的转述按工具结果规则处理 | +| `/defectify`、`/turmfluch` | 输入命中时中止模型调用,输出命中时替换正文 | +| 被动“xxx了”的模型判定 | 输入命中时中止模型调用 | +| `draw_svg` | 可见文本或说明命中时拒绝渲染,返回错误结果 | -过滤器只处理文本,包括用户输入、ASR 转写、图片转述和歌词。图片像素、音频波形、TTS 音频、生成图片和音乐成品不经过本过滤器。部署者仍需依赖相应 provider 的内容安全机制;项目当前不提供图片、音频或音乐 moderation provider。 +带工具调用的历史按完整对话轮检查触发消息、回复、工具参数与结果,以及原生块中的可读文本。命中或曾替换输出的历史轮次,在本次请求中使用清洗后的文本记录,保留正文与工具状态汇总,省略工具详情和原生签名块。后续预算精简沿用该清洗记录。 -`config/generation.toml` 中的 `prompt_blocklist` 继续作为生成业务专属限制,与`config/sensitive_words.toml` 叠加生效。前者适合记录特定生成模型不接受的提示词,后者是QuickQuip 各文本链路共享的部署级词表。 +过滤器处理文本。需要审核图片像素、音频波形、TTS 音频、生成图片或音乐成品时,在所用服务提供方确认并启用对应内容审核能力。每日总结与每日播报使用独立模型链路,当前未接入上述聊天过滤;词云直接渲染聊天词频,也未接入本词表。启用这些功能前,按各自数据来源评估内容要求。 -## 接入点 +`config/generation.toml` 的 `prompt_blocklist` 与本词表叠加生效:前者针对特定生成业务,后者在上表所列文本环节共用。 -主要文件:`src/quickquip/llm/service.py`、`src/quickquip/llm/tool_result_pipeline.py`、`src/quickquip/llm/single_shot.py`、`src/quickquip/llm/service_parts/draw_svg.py`、`src/quickquip/llm/service_parts/skills.py` 和`src/quickquip/adapters/nonebot/command_parts/media.py`。 +## 排障与词表保护 -| 接入点 | 位置 | 行为 | -|---|---|---| -| 输入侧 | prompt 准备好之后、调用 LLM 之前 | block → 返回 `DEFAULT_BLOCK_REPLY`,不调 LLM | -| 历史侧 | `list_recent_conversation_messages()` 取出后、注入 LLM 前 | block → `content`/`raw_content` 用 `[内容已屏蔽]` 替换,不修改数据库 | -| 输出侧 | LLM 响应取出后、写入 store 前 | block → 替换为 `DEFAULT_OUTPUT_FALLBACK`,写入历史的也是替换后的 | -| **工具参数** | `tool_registry.execute()` 调用前 | block → 直接拒绝执行,返回错误 result,节省 token + 防止外部 API 收到违规查询 | -| **工具结果** | `tool_registry.execute()` 返回后 | block 命中 ≤ 5 个且原文 ≥ 200 字 → scrub;否则整体替换为占位文本。两种分支都标记 `is_error=True`(让 LLM 知道结果不完整) | -| **Skill 描述** | Skill 目录扫描后、描述清单注入系统提示前 | block 命中 → 整只剔除该 Skill(不出现在清单与工具面) | -| **Skill 激活注入** | Skill 激活的指令正文并入会话前 | block → 本次不登记激活 | -| **图片/语音生成输入** | `/draw`、`/tts` 调用生成 provider 前 | block → 终止命令,不向 provider 提交 prompt 或引用文本 | -| **音乐生成输入** | 歌词生成或音乐生成 provider 调用前 | block → 终止命令,不提交 prompt、标题、歌词或引用文本 | -| **歌词输出** | 外部歌词生成完成后、发送或继续谱曲前 | block → 使用输出兜底回复,不发送歌词,也不把歌词提交给音乐 provider | -| **ASR 转写** | 转写文本并入普通 LLM prompt 后 | 复用输入侧扫描;原始音频会先发送给 ASR provider | -| **图片转述** | 视觉模型返回描述后、描述注入主 LLM 前 | block → 终止主 LLM 请求;视觉模型已经读取原始图片。非视觉模型的工具结果图片转述文本走上方「工具结果」扫描分支(按工具结果规则替换) | -| **故障化** | `/defectify` 直连 provider 的输入和输出边界 | 输入 block → 不调用 provider;输出 block → 使用输出兜底回复 | -| **turmfluch** | `/turmfluch` 一次性生成的输入(`turmfluch_input`)与输出(`turmfluch_output`)边界(`src/quickquip/llm/single_shot.py`) | 输入 block → 不调用 provider;输出 block → 使用输出兜底回复 | -| **STS card_le 输入** | `run_card_le_nearest()` 的 LLM 调用前(`card_le_input`,`src/quickquip/llm/single_shot.py`) | block → 不调用 LLM | -| **draw_svg 文本** | SVG 渲染前扫描可见文本 + caption(`src/quickquip/llm/service_parts/draw_svg.py`) | block → 拒绝渲染,返回错误结果 | - -**为什么工具结果扫描尤其重要**:搜索/抓取类工具(`search_web`、`fetch`、各类 MCP 工具)从外部源拉取内容,**用户的查询可以引导但我们无法预先审查**。一段富集敏感词的 tool_result 会作为 messages 的一部分进入下一轮 provider 请求,正是触发 DeepSeek `Content Exists Risk` / Aliyun `Content security warning` 的高危场景。 - -**没有接入的位置**: -- 图片像素、音频波形、生成图片、TTS 音频和音乐成品不属于文本过滤器的处理对象 -- `daily_summary` / `daily_briefing` 会走独立的模型级联 provider 调用,不经过 `LLMService.generate_reply()` 主链路,因此当前不会复用输入/输出/历史侧过滤器;如需加固,应在 `src/quickquip/llm/summarize.py` 与 `src/quickquip/llm/briefing.py` 的请求和响应边界接入同一个 `get_filter()` -- `wordcloud` 不调用 LLM,只读取群聊消息并渲染词频图片;如需避免敏感词出现在图片中,应在 `src/quickquip/chat/wordcloud.py` 的分词或渲染前增加扫描/剔除 - -## 性能 - -- 词表 ~1000 词,单条群聊消息(< 200 字符)扫描时间 < 0.5ms -- 词表 ~5000 词,扫描时间 < 1ms -- 自动机构建是一次性的(启动时或 `reload_filter()` 时),构建本身约 10-50ms - -如果词表规模超过 50k,应当切换到 `pyahocorasick` C 扩展。当前实现保留了相同的接口,切换只需改 `_AhoCorasick` 类的实现。 - -## 不要做的事 - -- ❌ 把 `config/sensitive_words.toml` 提交到公开仓库 -- ❌ 通过 Web Admin 或任何浏览器页面读取、回显、编辑 `config/sensitive_words.toml` -- ❌ 把命中日志记得太详细(如完整原文 + 用户 ID + 时间)——日志本身会成为合规风险 -- ❌ 在群里**告知用户**触发了过滤——直接静默 + 后台日志即可,告知等于教用户绕过 -- ❌ 让 LLM 自己判断“这内容能不能发”——增加成本和延迟,且模型自己也不可靠 -- ❌ 试图覆盖拼音、谐音、同形字等所有变体——误报会爆炸,得不偿失 - -## 测试 +命中日志包含环节、会话、类别和词条 SHA-256 的前 12 位,示例: -```bash -pytest tests/unit/common/test_sensitive_filter.py \ - tests/unit/adapters/test_media_sensitive_filter.py \ - tests/integration/test_multimodal_sensitive_filter.py \ - tests/integration/test_llm_service.py +```text +WARNING quickquip.common.sensitive_filter sensitive_filter[input] blocked scope=12345 hits=fraud:a1b2c3d4e5f6 ``` + +查找命中词时,在服务器本地对照词表计算哈希。群内收到的是对应环节的固定提示;具体词条通过后台日志排查。 + +| 现象 | 检查与处理 | +|---|---| +| 状态显示未加载 | 检查文件是否存在、词条是否为空,以及日志中的 TOML 解析错误;修复后重载 | +| 普通对话误拦截 | 对照命中类别与哈希定位词条,缩小词条范围或改为 `soft`,重载后复测 | +| 多词同时出现才应拦截 | 本词表采用逐词匹配;将需要语境判断的词先设为 `soft` 观察 | +| 更新文件后行为未变 | 执行重载并核对加载数量,确认修改的是 Bot 实际挂载的词表文件 | + +实际词表、命中原文和真实身份信息保存在部署环境。公开仓库使用模板;Web Admin 的状态接口保留汇总信息。开发者验证方法见[测试规范](../dev/testing.md)。 diff --git a/skills.example/self-docs/references/docs-admin-skills.md b/skills.example/self-docs/references/docs-admin-skills.md index c7d3f7bf..39153e85 100644 --- a/skills.example/self-docs/references/docs-admin-skills.md +++ b/skills.example/self-docs/references/docs-admin-skills.md @@ -8,37 +8,50 @@ Skill 是受信任的部署资产:部署者把技能包放进 `skills/` 目录 ## 部署目录 -运行目录为项目根的 `skills/`(已被 git 忽略),仓库随附的 `skills.example/` 承载官方预置 Skill 模板。部署照 `config/personas.example/` → `config/personas/` 的同一先例:从 `skills.example/` 复制或合并需要的 Skill 到 `skills/`,再按环境调整;Windows 懒人包首启(`start.bat`)会自动完成整目录复制。Docker 镜像与 Windows 懒人包均只携带 `skills.example/`;release 布局的容器化部署把 `skills/` 作为部署根共享目录(与 data/ 同级):bot 容器只读挂载、web-admin 容器读写挂载,Web 管理页的安装成果在容器重建与后续部署后仍然保留;每次部署把仓库 `skills/` 中新增的 Skill 非破坏性合并进共享目录(已存在文件不被覆盖、删除不传播),已安装 Skill 的更新与删除经 Web 管理页或服务器手动维护;目录供给细节见 `prod.example/` 模板。 +先确认 Bot 正在运行且已有正常 AI 对话,并能登录 Web 管理后台。在“LLM 工坊 → LLM 配置 → 工具与技能 → Skill 运行时”查看“Skill 目录”:留空使用 Bot 项目根的 `skills/`(已被 Git 忽略);非空使用指定目录,相对路径按项目根解析。文件应放在运行 Bot 的机器上。 + +将需要的技能包从 `skills.example/` 复制到实际目录。下面的 Bash 命令适用于 `skills.example/` 与目标 `skills/` 同在项目根的首次安装: + +```bash +mkdir -p skills +cp -r skills.example/self-docs skills/ +``` + +Windows 可在文件管理器创建目标 `skills` 文件夹,再复制整个 `self-docs` 文件夹,确认存在 `skills/self-docs/SKILL.md`。已有同名包时按下文“预置 Skill”节更新。自定义目录需替换命令目标;版本目录部署在部署根使用 `cp -r current/skills.example/self-docs skills/`,复制到共享目录。自建文本文件的保存步骤见[Skill 编写教程](../dev/skill-tutorial.md#43-第三步写-skillmd)。 + +Docker 镜像与 Windows 懒人包携带 `skills.example/`;Windows `start.bat` 首启自动复制整个目录。版本目录部署使用部署根共享 `skills/`:Bot 只读挂载,Web Admin 读写挂载,已安装内容随数据保留。每次部署合入仓库新增文件,现有文件和删除操作由 Web 管理页或服务器手动维护。目录供给见[生产模板](../../prod.example/README.md)。 + +直接使用根 Compose 模板时,需同时启用 Bot 与 Web Admin 的 `skills` 卷挂载;在项目根运行 `docker compose -f docker-compose.example.yml up -d quickquip web-admin` 应用新挂载。容器内的 Skill 目录对应挂载目标,服务器上传与文件管理则操作其主机来源目录。 目录约定: - 一个子目录一个 Skill,目录名即 Skill 名;只允许小写字母、数字和连字符(`^[a-z0-9][a-z0-9-]*$`,最长 64 字符),且必须与 `SKILL.md` frontmatter 里的 `name` 一致。 - `SKILL.md` 为 YAML frontmatter + Markdown 正文,必填 `name` 和 `description`;单文件上限 256KiB,`description` 上限 1024 字符。 -- `description` 是 AI 决定何时激活的唯一依据,必须写清触发条件(例如“当用户询问机器人用法或配置时使用”)。 +- `description` 帮助 AI 判断适用请求,应写清触发条件(例如“当用户询问机器人用法或配置时使用”)。 - 解析或校验不通过的 Skill 会被跳过并记录告警日志,不影响同目录的其他 Skill。 -## 配置(config/llm.toml `[skills]`) +## 配置(`settings.skills`) + +通过 Web Admin“LLM 配置”或配置 CLI 编辑并保存草稿,单独应用后由 Bot 接纳。资源上限与开关保存在 `data/llm_config.db`,Skill 内容继续由独立目录维护。完整流程见 [LLM 配置管理](llm-configuration.md)。 + +首次启用在“LLM 工坊 → LLM 配置 → 工具与技能”操作: + +1. 切到“工具与搜索”,在“工具调用轮次与上限”开启“启用工具调用”(`settings.runtime.tool_calling_enabled`)。 +2. 切到“Skill 运行时”,开启“启用 Skill”(`settings.skills.enabled`),确认“Skill 目录”。 +3. 默认空工具名单可保持。若“工具与搜索 → 内置工具白名单与发现”已有非空“额外启用工具”且“启用方式”为“替换”(`settings.tools.enabled_mode = "replace"`),将下表四个 Skill 工具名加入现有名单,保留其他需要的工具。 +4. 点击“保存草稿”,再点击“应用到 Bot”,等待状态条显示 Bot 已加载目标版本。应用持续排队时先检查 Bot 进程是否正在运行。 -| 键 | 说明 | 默认值 | -|----|------|--------| -| `enabled` | Skill 系统总开关 | `true` | -| `catalog_dir` | Skill 目录;留空 = 项目根 `skills/`,相对路径按项目根解析 | `""` | -| `catalog_max_bytes` | 系统提示中 Skill 清单的字节预算上限,实际预算取 min(模型上下文窗口 2%, 此值) | `8192` | -| `resource_max_bytes` | `read_skill_resource` 单次读取上限(字节) | `65536` | -| `search_max_results` | `search_skill_resources` 命中条数上限 | `50` | -| `search_max_output_bytes` | `search_skill_resources` 输出字节上限 | `32768` | -| `script_timeout_ms` | `run_skill_script` 默认超时(毫秒);单次调用可另行指定,硬上限 120000 | `30000` | -| `script_max_output_bytes` | 脚本 stdout/stderr 各自的输出字节上限,超限截断 | `65536` | +新建配置库的工具调用缺省关闭。工具发现设置见[工具发现配置](tool-discovery.md)。 -非法取值回退默认值并记录告警。`skills/` 为空目录或不存在时,Skill 工具不注册、系统提示不增加任何内容——未部署 Skill 的实例行为与此前完全一致。 +开关、目录及资源上限的完整字段表见[配置参考](configuration.md#settingsskills--skill-系统)。保存时会校验取值并返回具体字段路径及约束。目录为空或缺失时,模型使用基础工具与提示词。 -Skill 的增删就是部署侧的文件操作:目录在每次构建系统提示时重新扫描(每轮请求一次),无需重启,进行中的会话下一轮请求即可看到增删;catalog 块字节变化只影响当轮的前缀缓存命中。运行时(bot 进程与 AI 工具面)没有任何安装、更新或删除 Skill 的路径;部署侧的在线管理入口见「Web 管理与导入」。 +文件增删和内容更新在模型下一轮请求构建提示词时重新扫描并生效。LLM 配置中的开关、目录和资源参数通过保存草稿、应用生效。部署侧文件管理与在线入口见下文“Web 管理与导入”。 -群内 `/skill list` 可查看已安装 Skill 与当前会话已激活项(只读);与当前版本预置副本分叉的条目会附带更新提示(见「预置 Skill」节)。 +群内 `/skill list` 可查看已安装 Skill 与当前会话已激活项(只读);与当前版本预置副本分叉的条目会附带更新提示(见“预置 Skill”节)。安装 `self-docs` 后发送 `/ai 你的 /quote 命令怎么用?`,再看激活项是否出现 `self-docs`。名单总数取决于已有安装内容;验证时核对目标名称与激活结果。`/ai` 是默认前缀,已有自定义配置时使用对应前缀。 ## 工具面 -全部已安装 Skill 的 name + description 清单常驻系统提示,AI 据此语义匹配决定何时激活;激活后 `SKILL.md` 正文才进入对话。四个工具: +启用后,系统提示包含已安装 Skill 的名称和描述清单,AI 据此决定何时激活;激活后 `SKILL.md` 正文才进入对话。四个工具: | 工具 | 行为 | |------|------| @@ -47,18 +60,18 @@ Skill 的增删就是部署侧的文件操作:目录在每次构建系统提 | `search_skill_resources` | 在已激活 Skill 目录内按关键词或正则检索文本(需先激活) | | `run_skill_script` | 执行已激活 Skill `scripts/` 下的 `.py` / `.sh` 脚本(需先激活) | -脚本按扩展名映射解释器(`.py` → `python3`,`.sh` → `sh`),不依赖 shebang 与执行位;主机 PATH 上没有 `sh` 时 `.sh` 脚本直接报错拒绝执行(Windows 主机请使用 `.py` 脚本)。 +脚本按扩展名选择解释器:`.py` 优先使用 PATH 中的 `python3`,找不到时使用运行 Bot 的 Python;`.sh` 使用 PATH 中的 `sh`。解释器直接读取脚本,执行位与 shebang 不影响选择。Windows 主机优先使用 `.py`;需要 `.sh` 时须先在 Bot 环境提供 `sh`,缺失会返回错误。 ## 安全模型 -Skill 源由部署者严格把控——只放置审阅过的 Skill:其指令正文会进入对话上下文,脚本会在部署主机上执行。运行时的结构性防御: +Skill 源由部署者严格把控——只放置审阅过的 Skill:其指令正文会进入对话上下文,脚本会在部署主机上执行。运行时按以下规则读取资源和执行脚本: -- **无运行时变更路径**:AI 侧没有任何创建、修改或删除 Skill 文件的工具,Skill 内容只能经部署侧通道(文件操作或 Web Admin 管理页,见「Web 管理与导入」)变更。 +- **内容维护**:技能包由部署者通过文件操作或 Web Admin 管理页维护,AI 工具负责激活、读取、检索和执行随包脚本。 - **路径加固**:读取、检索、执行都限制在对应 Skill 目录内,拒绝 `..` 穿越、绝对路径与符号链接逃逸。 -- **脚本执行隔离**:脚本经结构化 argv 直接启动,无 shell,参数逐字传递不经解释层;子进程环境白名单仅 `PATH`/`LANG`/`TZ`,不继承 bot 进程环境,`.env` 中的凭证对脚本不可见;工作目录固定为该 Skill 目录。 +- **脚本环境**:按脚本类型启动解释器,参数逐项传递;工作目录为该 Skill 目录。子进程只继承 `PATH`/`LANG`/`TZ`,Bot 进程环境中的凭据不会传给脚本。 - **执行前复验**:脚本执行前做 SHA-256 快照比对,目录扫描之后内容有变化即拒绝执行。 -- **资源上限**:超时与输出上限见上表;目录内检索不起子进程,另有单次匹配 1s 引擎超时与单次调用 4s 墙钟预算兜底(病态正则最坏损失数秒,不会冻结实例);含嵌套量词或交叠分支的量化组、相邻可空量化原子链等病态正则形态会被静态检查拒绝(防灾难性回溯),被拒之模式可改用字面搜索或改写;scripts/ 单文件超 256KiB 不编入清单、不可执行。 -- **统一合规扫描**:Skill 相关的全部工具产出(清单描述、激活正文、资源内容、检索结果、脚本输出)与 `search_web` 等外部工具结果走同一敏感词扫描接缝,见 [sensitive-filter.md](sensitive-filter.md);`description` 命中拦截词的 Skill 会被整只从清单剔除并记录告警日志,不进入系统提示与激活面。 +- **资源上限**:超时与输出上限见配置参考;目录内检索不起子进程,另有单次匹配 1s 引擎超时与单次调用 4s 墙钟预算兜底(复杂正则可能占用数秒);含嵌套量词或交叠分支的量化组、相邻可空量化原子链等病态正则形态会被静态检查拒绝(防灾难性回溯),被拒绝的模式可改用字面搜索或改写;scripts/ 单文件超 256KiB 不编入清单、不可执行。 +- **统一合规扫描**:Skill 相关的全部工具产出(清单描述、激活正文、资源内容、检索结果、脚本输出)与 `search_web` 等外部工具结果走同一敏感词扫描流程,见 [sensitive-filter.md](sensitive-filter.md);`description` 命中拦截词的 Skill 会被从清单剔除并记录告警日志,不进入系统提示与激活面。 ### 禁止把 `run_skill_script` 当通用 shell @@ -73,37 +86,37 @@ Skill 源由部署者严格把控——只放置审阅过的 Skill:其指令 ### 版本对齐:漂移检测与同步 -预置 Skill 经复制到达 `skills/`;QuickQuip 版本升级会更新 `skills.example/`,已复制的本地副本不会随之自动更新。四层配套闭合这一摩擦: +预置 Skill 经复制到达 `skills/`;QuickQuip 版本升级会更新 `skills.example/`,已复制的本地副本不会随之自动更新。可通过以下入口查看差异并同步: -- **运行时漂移检测(只读)**:bot 每次扫描 Skill 目录时,对 `skills/` 与 `skills.example/` 中的同名 Skill 做全文件字节级指纹比对。分叉时记录 WARNING 日志(分叉名单变化才记录,同状态不逐轮重复;回归一致时记 INFO),`/skill list` 在对应条目标注「与当前版本预置不同,可运行 scripts/sync_preset_skills.py 更新」。检测结果只进日志与命令回复,不进入 catalog 块、系统提示与任何模型注入面,也不占用 catalog 预算。`skills.example/` 缺失时(pip 安装形态)检测自动跳过。 -- **同步脚本**:`python scripts/sync_preset_skills.py`(或 `--check`)报告每个预置 Skill 的四态——`current`(与预置副本一致)、`diverged`(已安装但不同)、`missing`(未安装)、`conflict`(存在同名非目录项或符号链接,需人工处理);发现冲突时退出码为 2,人工处理后重试。`--apply` 安装缺失项并把分叉项覆盖为预置副本,原副本整体备份为 `skills/.preset-backups/.preset-backup-<时间戳>`(备份容器自身无 SKILL.md,运行时扫描不感知;本地定制不丢,确认后自行清理)。脚本只依赖 Python 标准库,Docker / Linux 裸机 / Windows 形态通用;bot 每轮现扫 `skills/`,同步当轮生效,无需重启。 -- **部署入口**:`prod.example/deploy-v4.sh` / `deploy-v4.ps1` 在 deploy、dry-run 与 migrate 时自动执行一次 `--check` 报告并给出同步命令(best-effort,不阻断部署);源码形态部署在版本升级后按 Release notes 的「预置 Skill 变动」小节指引手动执行同步(该小节是 release PR 模板的固定检查项)。 -- **Web 管理面**:Web Admin「Skill」页顶面板按同一判定语义展示各预置 Skill 状态,支持勾选同步与一键同步全部待处理,覆盖分叉项前同样自动备份至 `skills/.preset-backups/`;详见「Web 管理与导入」。 +- **运行时漂移检测(只读)**:bot 每次扫描 Skill 目录时,对 `skills/` 与 `skills.example/` 中的同名 Skill 做全文件字节级指纹比对。分叉时记录 WARNING 日志(分叉名单变化才记录,同状态只记录一次;回归一致时记 INFO),`/skill list` 在对应条目标注“与当前版本预置不同,可运行 scripts/sync_preset_skills.py 更新”。检测结果只进日志与命令回复,不进入 catalog 块、系统提示与任何模型注入面,也不占用 catalog 预算。`skills.example/` 缺失时(pip 安装形态)检测自动跳过。 +- **同步脚本**:`python scripts/sync_preset_skills.py`(或 `--check`)报告每个预置 Skill 的四态——`current`(与预置副本一致)、`diverged`(已安装但不同)、`missing`(未安装)、`conflict`(存在同名非目录项或符号链接,需人工处理);发现冲突时退出码为 2,人工处理后重试。`--apply` 安装缺失项并把分叉项覆盖为预置副本,原副本整体备份为 `skills/.preset-backups/.preset-backup-<时间戳>`(备份容器自身无 SKILL.md,运行时扫描不感知;备份保存原有定制,确认后自行清理)。脚本只依赖 Python 标准库,Docker / Linux 裸机 / Windows 形态通用;bot 每轮现扫 `skills/`,下一轮请求读取同步后的文件,无需重启。 +- **部署入口**:`prod.example/deploy-v4.sh` / `deploy-v4.ps1` 在 deploy、dry-run 与 migrate 时自动执行一次 `--check` 报告并给出同步命令(best-effort,不阻断部署);源码形态部署在版本升级后按 Release notes 的“预置 Skill 变动”小节指引手动执行同步(该小节是 release PR 模板的固定检查项)。 +- **Web 管理面**:Web Admin“Skill”页顶面板按同一判定语义展示各预置 Skill 状态,支持勾选同步与一键同步全部待处理,覆盖分叉项前同样自动备份至 `skills/.preset-backups/`;详见“Web 管理与导入”。 ## Web 管理与导入 -Web Admin 导航「LLM 工坊」区的「Skill」页(`/ops/#/skills`)提供 Skill 目录的在线管理面,作用目录与 `[skills].catalog_dir` 的生效目录一致。所有写入直接落盘:运行时下次构建系统提示即重新扫描目录,保存即热生效,无需 reload 或重启。容器化部署下写入落在部署根共享 `skills/`(web-admin 容器读写挂载),容器重建与版本部署后仍然保留;部署流程只向共享目录合入仓库新增的 Skill,不覆盖也不删除已有内容。全部写操作(新建、编辑、删除、预置同步、安装)记入审计日志(`data/audit.db`,Web Admin 审计页可见)。 +Web Admin 导航“LLM 工坊”区的“Skill”页(`/ops/#/skills`)提供 Skill 目录的在线管理面,作用目录与 `settings.skills.catalog_dir` 的生效目录一致。所有写入直接落盘:运行时下次构建系统提示即重新扫描目录,下一轮请求读取更新后的文件,无需重启。容器化部署下写入落在部署根共享 `skills/`(web-admin 容器读写挂载),容器重建与版本部署后仍然保留;部署流程只向共享目录合入仓库新增的 Skill,不覆盖也不删除已有内容。全部写操作(新建、编辑、删除、预置同步、安装)记入审计日志(`data/audit.db`,Web Admin 审计页可见)。 ### 管理与在线编辑 -- 列表覆盖目录内全部 Skill:解析或校验不通过、被运行时扫描静默跳过的坏项同样列出并附诊断(缺 `SKILL.md`、非 UTF-8、frontmatter 校验失败等);与预置副本一致/已偏离的条目附预置态标注,含 `scripts/` 的条目附「含脚本」标注。 +- 列表覆盖目录内全部 Skill:解析或校验不通过、被运行时扫描静默跳过的坏项同样列出并附诊断(缺 `SKILL.md`、非 UTF-8、frontmatter 校验失败等);与预置副本一致/已偏离的条目附预置态标注,含 `scripts/` 的条目附“含脚本”标注。 - 详情页展示 frontmatter metadata、资源清单(路径、类型、大小)与诊断;`SKILL.md`、`references/`、`scripts/` 等文本资源可在线编辑或删除(`SKILL.md` 不可删,保存时按运行时同一 parser 校验),单文件读写上限 256KiB。 - 新建 Skill 校验目录名规范(`^[a-z0-9][a-z0-9-]*$`,≤64 字符)并生成 frontmatter 骨架;删除 Skill 移除整个目录,不可恢复。 ### 预置同步(页面内) -页顶「预置同步」面板与 `scripts/sync_preset_skills.py` 同一判定语义,逐项报告 `current`(已安装,与预置副本一致)/ `diverged`(已安装,与预置副本不同)/ `missing`(未安装)/ `conflict`(存在同名非目录项,需人工处理);仅本地(非预置)的 Skill 单独列出。勾选后「同步所选」,或「一键同步全部待处理」;`diverged` 项覆盖前旧副本整体备份至 `skills/.preset-backups/`,确认本地定制无丢失后自行清理。 +页顶“预置同步”面板与 `scripts/sync_preset_skills.py` 同一判定语义,逐项报告 `current`(已安装,与预置副本一致)/ `diverged`(已安装,与预置副本不同)/ `missing`(未安装)/ `conflict`(存在同名非目录项,需人工处理);仅本地(非预置)的 Skill 单独列出。勾选后“同步所选”,或“一键同步全部待处理”;`diverged` 项覆盖前旧副本整体备份至 `skills/.preset-backups/`,确认本地定制无丢失后自行清理。 ### 安装第三方 Skill 三种来源:本地 `.zip` 压缩包(≤16MiB)、本地文件夹(整目录上传)、GitHub 仓库链接。包内每个含 `SKILL.md` 的目录都会成为一个候选;安装以 frontmatter `name` 为目录名,压缩包内的原始目录名不参与校验。与现有 Skill 同名时须显式勾选覆盖安装,旧副本自动备份至 `skills/.preset-backups/`。 -安装是两阶段「检查 → 确认」交互:检查阶段只读产出候选报告——`description`、文件数与体积、解析诊断、是否携带 `scripts/` 可执行脚本(含脚本时红色警示并列出脚本清单);确认后才写入目录。检查载荷在服务端暂存 30 分钟,超时需重新检查。候选验收与运行时扫描共用同一 parser(Agent Skills 开放标准的可移植核心),通过检查的 Skill 安装后即可被运行时装载。 +安装是两阶段“检查 → 确认”交互:检查阶段只读产出候选报告——`description`、文件数与体积、解析诊断、是否携带 `scripts/` 可执行脚本(含脚本时红色警示并列出脚本清单);确认后才写入目录。检查载荷在服务端暂存 30 分钟,超时需重新检查。候选验收与运行时扫描共用同一 parser(Agent Skills 开放标准的可移植核心),通过检查的 Skill 安装后即可被运行时装载。 -摄取护栏:zip 条目 ≤500、单文件 ≤1MiB、解压总量 ≤32MiB;拒绝符号链接与绝对路径条目。 +导入大小限制:zip 条目 ≤500、单文件 ≤1MiB、解压总量 ≤32MiB;拒绝符号链接与绝对路径条目。 -GitHub 导入经 `codeload.github.com` 下载仓库 zip:仅支持公开仓库,下载硬上限 32MiB、超时 20s,部署主机需可访问 `github.com` / `codeload.github.com`。接受 `https://github.com//`、`…/tree/` 与 `…/tree//<子目录>` 三种链接;多 Skill 仓库(monorepo)必须用 `/tree/` 子目录链接定位单个 Skill——按整仓摄取会把仓库其余部分一并计入护栏,容易触发条目数/体积上限;`/blob/` 单文件链接不受支持。 +GitHub 导入经 `codeload.github.com` 下载仓库 zip:仅支持公开仓库,下载硬上限 32MiB、超时 20s,部署主机需可访问 `github.com` / `codeload.github.com`。接受 `https://github.com//`、`…/tree/` 与 `…/tree//<子目录>` 三种链接;多 Skill 仓库(monorepo)必须用 `/tree/` 子目录链接定位单个 Skill——按整仓摄取会把仓库其余部分一并计入大小限制,容易触发条目数/体积上限;`/blob/` 单文件链接不受支持。 ### Web 管理面的安全边界 -项目安全原则是「尽提醒义务的最高自由度」:Web 管理面把 Skill 写入与导入能力开放给全部持有管理会话的人,页面在列表「含脚本」标注、脚本编辑横幅、安装检查报告的红色警示等处尽提醒义务,但不替代人工审阅。「只放置审阅过的 Skill」这一要求不变——Skill 可携带以 bot 进程权限在部署主机执行的 `scripts/` 脚本,导入第三方 Skill 前必须逐文件审阅其内容,检查报告即审阅界面。Web 写侧与运行时共用路径加固(拒绝 `..` 穿越、绝对路径与符号链接逃逸);运行时的脚本执行隔离、执行前复验与资源上限对在线编辑/安装的 Skill 同样生效。 +持有 Web 管理会话的人可以写入和导入 Skill。页面在含脚本标注、编辑横幅和安装报告中列出可执行内容,部署者须在确认安装前逐文件审阅。脚本以 Bot 进程权限在部署主机执行;上文的环境变量限制、路径检查和资源上限同样适用于在线安装与编辑的 Skill。 diff --git a/skills.example/self-docs/references/docs-admin-tool-discovery.md b/skills.example/self-docs/references/docs-admin-tool-discovery.md index 4ada4d8c..8346a864 100644 --- a/skills.example/self-docs/references/docs-admin-tool-discovery.md +++ b/skills.example/self-docs/references/docs-admin-tool-discovery.md @@ -2,44 +2,56 @@ # LLM 工具发现配置 -本文面向部署者和管理员,说明如何配置本地 `tool_search` 工具发现。该功能适合接入大量 MCP 工具时使用,例如 GitHub MCP 一次暴露几十个工具的场景。 +本文面向部署者和管理员,说明如何让模型从大量工具中按需搜索并加载能力。适合 GitHub MCP 等工具较多的服务。 + +先在“LLM 配置 → 工具与技能”开启工具调用。下方示例为完整导出 JSON 的字段片段,可在页面中编辑,或合入完整导出文件后预览导入。保存草稿并单独应用,确认 Bot 已加载目标版本。完整流程见[LLM 配置管理](llm-configuration.md)。 --- ## 1. 功能作用 -工具发现开启后,QuickQuip 不会在每次 LLM 请求里暴露全部工具定义。初始请求只包含少量常驻工具;模型需要其它能力时,先调用 `tool_search` 搜索工具目录,工具循环会把匹配到的真实工具加入下一轮请求。 +工具发现开启后,初始请求包含常驻工具,模型需要其他能力时先调用 `tool_search` 搜索目录。匹配的工具加入下一轮请求,模型随后调用这些工具。 -这可以降低 prompt 和 tool schema 体积,并减少模型在大量工具中选错工具的概率。 +按需加载可以减少每轮发送的工具定义数量。 --- ## 2. 推荐配置 -在 `config/llm.toml` 中配置: - -```toml -[tools] -enabled = [] - -discovery_mode = "auto" -discovery_min_tools = 10 -discovery_search_limit = 5 -discovery_max_loaded_tools = 12 -# 留空时使用内置默认集;自行列出时建议保留下列六项(activate_skill 供 Skill 系统激活使用) -always_loaded = ["tool_search", "tool_list", "get_identity", "list_memories", "search_web", "activate_skill"] +在 Web Admin“LLM 配置”编辑 `settings.tools`,或在完整导出 JSON 中配置以下字段: + +```json +{ + "settings": { + "tools": { + "enabled": [], + "discovery_mode": "auto", + "discovery_min_tools": 10, + "discovery_search_limit": 5, + "discovery_max_loaded_tools": 12, + "always_loaded": [ + "tool_search", + "tool_list", + "get_identity", + "list_memories", + "search_web", + "activate_skill" + ] + } + } +} ``` 字段说明: | 键 | 说明 | |----|------| -| `enabled` | 工具白名单。为空时启用内置工具和已连接的 MCP 工具;v1.12 起非空时默认 append(追加),`enabled_mode = "replace"` 才是精确白名单(详见 configuration.md 升级说明) | +| `enabled` | 工具名单。空列表启用默认内置工具和已连接的 MCP 工具;非空时默认追加,`enabled_mode = "replace"` 按名单精确过滤,见[配置参考](configuration.md#settingstools--工具调用) | | `discovery_mode` | `off` 全量暴露;`on` 强制工具发现(`tool_search` 被白名单排除或当前没有可延迟工具时不生效);`auto` 超过阈值后自动启用 | | `discovery_min_tools` | `auto` 模式下,可延迟工具数超过该值才启用工具发现 | | `discovery_search_limit` | 单次 `tool_search` 最多返回并加载的工具数;同一上限也约束 `tool_list mode = "load"` 的单次加载数 | -| `discovery_max_loaded_tools` | 一次工具调用循环中已加载工具的总数上限(`always_loaded` 常驻工具计入) | -| `always_loaded` | 工具发现开启时仍然直接暴露的常驻工具。未配置时回退内置默认集:`tool_search` / `tool_list` / `get_identity` / `list_memories` / `search_web` / `activate_skill` | +| `discovery_max_loaded_tools` | 新增工具时比较当前已加载总数,`always_loaded` 常驻工具计入;达到该值后停止新增,已有集合保留 | +| `always_loaded` | 工具发现开启时直接提供给模型的常驻工具。未配置时回退内置默认集:`tool_search` / `tool_list` / `get_identity` / `list_memories` / `search_web` / `activate_skill` | `tool_search` 用于按能力描述搜索工具;`tool_list` 用于列出工具组、工具名、工具摘要,并可用 `mode = "load"` 按精确名称加载工具。 @@ -63,65 +75,63 @@ always_loaded = ["tool_search", "tool_list", "get_identity", "list_memories", "s ## 4. 常驻工具建议 -建议保留: - -- `tool_search` -- `tool_list` -- `get_identity` -- `list_memories` -- `search_web` -- `activate_skill`(Skill 系统的激活入口;未部署 Skill 时无效果) +推荐配置中的六个常驻工具提供搜索、目录、身份、记忆、联网与 Skill 激活能力,可按实际使用需求调整。 如果某个 MCP 工具使用频率很高,也可以加入 `always_loaded`。例如: -```toml -always_loaded = [ - "tool_search", - "tool_list", - "get_identity", - "list_memories", - "search_web", - "mcp_github_search_repositories" -] +```json +{ + "settings": { + "tools": { + "always_loaded": [ + "tool_search", + "tool_list", + "get_identity", + "list_memories", + "search_web", + "mcp_github_search_repositories" + ] + } + } +} ``` --- ## 5. GitHub MCP 场景 -GitHub MCP 工具数量较多时,建议: - -```toml -[tools] -discovery_mode = "auto" -discovery_min_tools = 10 -discovery_search_limit = 5 -discovery_max_loaded_tools = 12 -always_loaded = ["tool_search", "tool_list", "get_identity", "list_memories", "search_web", "activate_skill"] -``` - -如果希望模型总是先搜索 GitHub 能力,再调用具体 GitHub 工具,保持 GitHub MCP 工具不在 `always_loaded` 中即可。 +使用上方推荐的 `auto` 配置,让 GitHub 工具进入按需加载目录。需要每轮直接提供的高频工具可加入 `always_loaded`。 生产环境建议在 MCP server 层先收窄工具集合,再启用工具发现。例如只接入常用读类工具: -```toml -[[mcp.servers]] -id = "github" -transport = "http" -tool_prefix = "github" -url = "https://mcp.example.com/github/mcp" -headers = { Authorization = "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" } -include_tools = [ - "search_repositories", - "search_code", - "get_file_contents", - "list_issues", - "issue_read", - "list_pull_requests", - "pull_request_read", - "actions_list", - "actions_get", -] +```json +{ + "settings": { + "mcp": { + "servers": [ + { + "id": "github", + "transport": "http", + "url": "https://mcp.example.com/github/mcp", + "headers": { + "Authorization": "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" + }, + "include_tools": [ + "search_repositories", + "search_code", + "get_file_contents", + "list_issues", + "issue_read", + "list_pull_requests", + "pull_request_read", + "actions_list", + "actions_get" + ] + } + ] + } + } +} ``` 被 `include_tools` / `exclude_tools` 过滤掉的工具不会进入 QuickQuip 工具注册表,因此也不会出现在 `tool_search`、`tool_list` 或真实工具调用路径中。 @@ -138,7 +148,7 @@ include_tools = [ 检查: -- `[tools].enabled` 是否把目标工具排除 +- `settings.tools.enabled` 是否把目标工具排除 - MCP server 是否连接成功 - `/llm mcp status` 是否能看到对应工具 - 提问里是否包含工具来源或能力关键词 @@ -149,13 +159,18 @@ include_tools = [ 2. `tool_list mode="group" group="mcp:github"` 查看某组摘要 3. `tool_list mode="load" names=["目标工具名"]` 精确加载工具 -### 想临时恢复旧行为 +### 临时直接提供全部工具 设置: -```toml -[tools] -discovery_mode = "off" +```json +{ + "settings": { + "tools": { + "discovery_mode": "off" + } + } +} ``` -然后重载 LLM 配置。 +然后保存草稿并应用;等待配置任务完成并确认 Bot 实际加载版本。 diff --git a/skills.example/self-docs/references/docs-admin-web-admin.md b/skills.example/self-docs/references/docs-admin-web-admin.md index c411f550..1511dbef 100644 --- a/skills.example/self-docs/references/docs-admin-web-admin.md +++ b/skills.example/self-docs/references/docs-admin-web-admin.md @@ -2,7 +2,15 @@ # Web Admin 管理后台 -本文档记录 QuickQuip Web 管理后台(`/ops/`)的鉴权结构、部署注意事项和功能列表。 +本文面向部署者和管理员,说明如何启动 QuickQuip Web 管理后台(`/ops/`)、配置登录与反向代理,并找到各功能入口。 + +## 第一次打开后台 + +1. 在根 `.env` 中填写 `WEB_ADMIN_PASSWORD` 作为管理口令。按 [README](../../README.md#安装步骤)启动源码版后台,或按[部署指南](deployment.md)启动容器;Windows 懒人包运行 `start.bat` 会同时启动后台和 Bot。 +2. 后台在自己电脑上运行时,浏览器打开 `http://127.0.0.1:5104/ops/`。部署在服务器上时,按[服务器后台访问步骤](deployment.md#7-web-管理后台)建立 SSH 隧道,再打开同一地址。 +3. 在登录页输入第 1 步的口令。进入后台概览后,可从侧栏进入“LLM 工坊 → LLM 配置”,按[首次启用 LLM](llm-configuration.md#首次启用-llm)配置一次群聊对话。 + +浏览器连接失败时检查后台进程或容器是否正在运行;服务器部署同时检查隧道窗口。修改 `.env` 后,源码或懒人包重新启动后台进程,容器按[环境变量更新步骤](deployment.md#日常维护)重建后台,使口令生效。 --- @@ -20,12 +28,12 @@ FastAPI web-admin SQLite / 文件系统 ``` -当前版本采用“双层门”: +配置 nginx `auth_basic` 的 HTTPS 部署采用“双层门”: - **外层**:nginx `auth_basic` - **内层**:QuickQuip 自身的应用层 session 登录 -这意味着即使 nginx 外层配置出现遗漏,FastAPI 里的管理接口仍然不会直接裸露。内层登录还带速率限制:同一 IP 在 60 秒内登录失败达到 5 次后被临时封禁 300 秒,封禁期间的登录请求直接返回 429。 +FastAPI 管理接口独立校验应用会话。内层登录带速率限制:同一 IP 在 60 秒内登录失败达到 5 次后被临时封禁 300 秒,封禁期间的登录请求直接返回 429。 --- @@ -46,7 +54,7 @@ FastAPI web-admin - 介质:SQLite - 内容:`session_id`、创建时间、过期时间、最近访问时间、客户端 IP、User-Agent -前端**不会**持久化管理员口令,也不会把长期 token 写进 `localStorage` 或打进 JS bundle。 +管理员登录后,浏览器通过会话 Cookie 访问后台;口令只用于登录校验。 ### 3. cookie 属性 @@ -61,25 +69,13 @@ FastAPI web-admin ### 4. 路由保护 -除以下接口外,所有 `/ops/api/*` 路由都会统一执行 `require_admin_session`: +所有业务管理接口都校验应用会话;登录状态查询、登录和退出使用以下接口: - `GET /ops/api/auth/me` - `POST /ops/api/auth/login` - `POST /ops/api/auth/logout` -业务路由本身不再假设“只要能访问到 FastAPI 就一定已经认证过”。 - ---- - -## 为什么不用前端 Bearer Token - -当前实现明确没有采用“登录后把长期 token 存进 `localStorage`,再用 `Authorization: Bearer ...` 调接口”的方案,原因是: - -- 主密钥会长期暴露给浏览器 JS 运行环境 -- 没有真正的服务端会话失效能力 -- 退出登录语义较弱,本质上更接近“把主钥匙存到前端” - -QuickQuip 当前是一个同源 Vue SPA + FastAPI 后台,做服务端 session 更自然,也更容易和现有部署保持解耦。 +业务接口的读写权限来自应用登录会话。 --- @@ -99,7 +95,7 @@ WEB_ADMIN_COOKIE_SECURE=auto - `WEB_ADMIN_SESSION_TTL_HOURS` session 续期窗口,默认 `168` - `WEB_ADMIN_COOKIE_SECURE` `auto | true | false` -`web_api.py` 会在启动时读取项目环境变量文件,并允许运行环境通过同名变量覆盖默认值。 +在根目录 `.env` 设置这些变量后,源码安装重新启动 Web 进程,容器安装重新创建 Web 服务。源码安装先在 `frontend/` 构建前端,再从项目根运行 `python web_api.py`;容器安装与变量更新步骤见[部署指南](deployment.md)。`web_api.py` 启动时读取根 `.env`,已有进程环境变量优先。 --- @@ -129,7 +125,7 @@ WEB_ADMIN_COOKIE_SECURE=true - 浏览器会话使用 `SameSite=Strict` - 写操作会额外检查 `Origin` / `Referer` 是否与当前请求宿主一致 -- FastAPI 层自身有登录态校验,不再完全依赖 nginx +- FastAPI 独立校验应用登录会话 外层站点访问控制和应用层 session 共同组成后台的纵深防御边界。 @@ -137,33 +133,33 @@ WEB_ADMIN_COOKIE_SECURE=true ## 功能标签页 -Web Admin 当前提供 29 个标签页(前端使用 vue-router 4 hash 模式,深链接形如 `/ops/#/stats`)。前端使用响应式设计、亮色/暗色主题切换,以及一套以 QQ 蓝为主色、青/琥珀为辅助色的设计 token 系统:氛围层(侧栏/状态条/抽屉/Toast)采用半透玻璃浮于克制动效的粒子光场之上,内容区(卡片/表格/表单)保持实色以保证可读性;全局缓动为 linear/steps 机械风格,换页时顶部有一道光带横扫。 +后台提供 29 个功能页面,侧栏可直接进入,深链接形如 `/ops/#/stats`。页面支持不同屏幕尺寸及亮色/暗色主题。 - **概览** — 汇总运行状态、常用入口和关键指标 - **统计** — 各群消息数、活跃用户排行、规则触发 Top - **规则** — 按群启用/禁用任意规则,toggle 实时生效 - **群组** — 每日总结 / 每日播报 / 群周报 / 群月报群管理(按群开关、立即生成) -- **群 LLM** — 按群覆盖 provider/model/persona/前缀/历史条数等 runtime 字段、思考档位(reasoning_effort,三态:六档或跟随 provider 配置档,与 `/llm effort` 同一配置面),以及 Agent Loop 分段交付两域开关(中间轮发送 / 最终轮分段,与 `/llm delivery` 同一配置面);列表会同时显示近期活跃群和数据库里已有覆盖配置的群 +- **群 LLM** — 按群设置渠道、模型、人格、触发前缀与上下文条数,选择思考档位,并控制过程回复与最终长回复分段;分别对应 `/llm effort`、`/llm delivery` 等命令。列表同时显示近期活跃群和已有配置的会话 - **唤醒** — 按群查看并编辑唤醒参数,切换 `awakening_*` 规则和无聊唤醒 opt-in;兴趣话题由人格配置和规则开关控制 - **限流** — 实时限流观测(按 scope 分全局/按群视图,5s 可选自动刷新) - **记忆** — 按群浏览与编辑 LLM 长期记忆,支持明确选择、替换和删除成员引用,提供原文查看 -- **对话** — 按群浏览 LLM 对话历史(含私聊/归档,支持关键词过滤、游标翻页)。群聊和私聊的消息删除经任务队列交由 Bot 执行:回复行删除对应 Turn 正文,用户触发行删除关联对话轮;归档会话提供只读浏览。页面在收到确认删除结果后刷新消息及计数,排队期间保留原消息;等待超时可继续查询原任务,服务端任务仍可执行。 +- **对话** — 按群浏览 LLM 对话历史(含私聊/归档,支持关键词筛选和翻页)。群聊和私聊的消息删除经任务队列交由 Bot 执行:回复行删除对应 Turn 正文,用户触发行删除关联对话轮;归档会话提供只读浏览。页面在收到确认删除结果后刷新消息及计数,排队期间保留原消息;等待超时可继续查询原任务,服务端任务仍可执行。 - **人格** — 在线编辑 `config/personas/*.toml`(含新建/删除,`_shared.toml` 保护) - **资料** — 在线编辑 `llm_about/vocab.yaml`、`llm_about/identities.yaml` 及群级覆盖文件(保存后执行 `/llm reload` 或重启 bot 生效) -- **Skill** — Skill 目录管理(「LLM 工坊」区,`/ops/#/skills`):列表含扫描坏项诊断与预置态标注,详情页可在线编辑/删除 `SKILL.md` 与 `scripts/` 等文本资源,写入即热生效;页内预置同步对照 `skills.example/` 勾选或一键对齐(覆盖前自动备份 `.preset-backups/`);安装向导支持本地压缩包/文件夹/GitHub 仓库链接三种来源,两阶段「检查 → 确认」出示候选报告(含可执行脚本红色警示)。格式约束与安全边界见 [skills.md](skills.md) -- **诊断** — LLM runtime 重载、MCP 重连、上下文清理、样本请求、文本规则回归测试、provider 探活(并发,按需计费)和 LLM 健康状态 -- **MCP** — MCP 服务器状态面板(transport、连接状态、工具数量、错误信息,支持 bot 与 web-admin 共享状态文件) -- **用量** — LLM 用量/成本看板(provider/模型/功能/群/人格五维 breakdown 与筛选、定价状态展示) -- **纪元** — 会话纪元运行态看板:锯齿时间轴(保留条数 / 窗口 tokens / 输入构成三模式)、锚点推进事件(冷场/触顶/行数兜底/换人格四色悬崖与纪元分段,事件 chips 逐事件回放)、窗口构成条(锚点罩住的对话区间,仅消息元数据不含正文)、信封构成条(最近一封【轮次上下文】的六段 token 分解)、KPI 行与冷场倒计时环。实时态经动作队列由 bot 进程快照回传,点击主图任意时刻可把构成条定格到该时刻 -- **总结** — 查阅/删除每日总结、群周报、群月报存档(顶部切换日/周/月);「生成健康度」按链路汇总近 7/30 天日报、简报和周月报的调用次数、接受率、异常构成、成本与均耗时。一次级联可产生多次尝试,成本包含已丢弃正文的调用;旧记录缺少正文接受结果时计入未知。每篇报文详情内的「生成日志」展示为得到该报文经历的级联各跳(时间点、模型、耗时、token、finish_reason、采纳/丢弃结果);1.15.3 起每次生成携带 run_id 精确归因,历史报文按生成时间窗推算 +- **Skill** — 查看技能包状态与诊断、编辑文本资源、同步预置包,并从压缩包、文件夹或 GitHub 导入。安装先检查再确认,覆盖前自动备份;含脚本的包会提示审阅。入口为“LLM 工坊”的 `/ops/#/skills`,操作与约束见[Skill 管理](skills.md) +- **诊断** — 已发布 LLM 配置重载、MCP 手动重连、上下文清理、样本请求、文本规则回归测试、provider 探活(Bot 队列逐目标执行,按需计费)和 LLM 健康状态 +- **用量** — LLM 用量/成本看板(渠道/模型/功能/群/人格五维统计与筛选、定价状态展示) +- **纪元** — 查看会话窗口条数、token 及输入构成随时间的变化,按冷场、触顶、行数限制和切换人格事件回看窗口推进。显示轮次上下文构成、实时水位与冷场倒计时,点击时间点可定格查看 +- **总结** — 查阅/删除每日总结、群周报、群月报存档(顶部切换日/周/月);“生成健康度”按链路汇总近 7/30 天日报、简报和周月报的调用次数、接受率、异常构成、成本与均耗时。一次级联可产生多次尝试,成本包含已丢弃正文的调用;旧记录缺少正文接受结果时计入未知。每篇报文详情内的“生成日志”展示为得到该报文经历的级联各跳(时间点、模型、耗时、token、finish_reason、采纳/丢弃结果);1.15.3 起每次生成携带 run_id 精确归因,历史报文按生成时间窗推算 - **语录** — 语录管理(按群浏览、关键词搜索、删除;发言人优先显示标准身份及 QQ,改名时附收藏时原名片;正文使用当前身份并提供原文查看) - **贴吧** — 贴吧帖子池浏览(同步状态/关键词搜索/图文详情/立即同步/实时抓取) - **词云** — 词云生成(today/week/month/year 时间窗、Top 词频排行、图片下载) -- **配置** — `config/llm.toml`、`config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 多文件 TOML 编辑器;保存后按文件返回生效方式(`awakening`/`chat_rules` 自动重载,`llm` 引导手动 reload,其余需重启)。`llm.toml` 在文本编辑区上方另带 provider 级思考档位结构化面板:行级修改只动目标 provider 的 `reasoning_effort` 行、注释与其余内容原样保留,目标段含多行字符串时拒绝并引导改用文本编辑 +- **LLM 配置** — “LLM 工坊”的 `/ops/#/llm-config` 提供六模块工作台,维护渠道、对话、工具、报告、价格与发布历史。支持草稿保存、应用、版本备注、JSON 交换、历史恢复及排队任务取消;MCP 状态在“工具与技能”中查看。旧模块深链与 `/ops/#/mcp-dashboard` 自动定位到对应位置。完整操作与状态说明见[LLM 配置管理](llm-configuration.md) +- **配置** — `config/generation.toml`、`config/chat_rules.toml`、`config/games.toml`、`config/awakening.toml`、`config/niuniu_text.toml`、`config/niuniu_text_safe.toml` 多文件 TOML 编辑器;`awakening`/`chat_rules` 保存后自动重载,其余需重启。 - **实时日志** — 当前运行日志流、连接状态与当前文件下载 - **LLM Trace** — 按 HTTP 调用索引 QuickQuip 与 LLM Provider 之间的完整 JSON 请求/响应文本,支持持久开关、实时状态更新、分页和按需加载正文 - **日志归档** — 历史轮转日志浏览、预览与下载 -- **调度器监控** — APScheduler 全部任务(节日问候、每日总结/播报、定时消息等)的只读运行时面板(job ID、trigger、next_run、last_run 时间与状态、错误详情)。调度器运行在 bot 进程内,bot 端每 30 秒把任务快照与最近执行结果写入 `data/cron_jobs.json`,本页读取该共享状态文件;bot 启动时从该文件恢复各任务的最近执行结果(重启后「上次执行」不丢,尚未运行过的任务显示「未执行」);群聊定时消息的增删改在「定时消息」页 +- **调度器监控** — 查看节日问候、报告、定时消息等任务的计划、下次和上次执行时间、状态及错误。Bot 每 30 秒更新共享状态文件 `data/cron_jobs.json`,重启后恢复上次执行结果。定时消息的编辑入口在“定时消息”页 - **定时消息** — 群聊定时消息管理(新建/编辑/启停/删除,支持固定文案与 LLM 任务两种类型及一次性任务;任务存于 `data/scheduled_messages.json`,保存后经动作队列通知 bot 进程重注册)。表单分简易/高级两种模式:简易模式用频次(每天/每周/每月/仅一次)+ 时间/日期选择器自动组装 cron,群号从已知群列表勾选;高级模式保留手写 5 段 cron。选择"仅一次"即一次性任务(触发后自动删除),其触发日期时间必须在未来——若钉死月/日的 cron 今年对应时刻已过,后端会拒绝创建,避免静默等到来年 - **审计** — Web Admin 变更审计日志(按操作类型、目标类型、操作人、日期范围等条件过滤,分页浏览) - **金币** — 金币经济面板(各群金币汇总、排行 TOP 20、账户查询、手动余额调整并记录审计日志) @@ -171,33 +167,26 @@ Web Admin 当前提供 29 个标签页(前端使用 vue-router 4 hash 模式 敏感词过滤器没有独立标签页。后台提供只读接口 `GET /ops/api/sensitive-filter/status`,LLM 健康检查也会汇总过滤器加载状态和词表数量。`config/sensitive_words.toml` 属于高敏部署文件,只在服务器本地维护,Web Admin 不提供内容读取或在线编辑入口。 -纪元看板的数据口径:主图锯齿取自用量库每轮单值(按 Agent Loop 去重,同轮多次调用不重复计数);锚点推进事件由 bot 进程在推进时旁路落库(`epoch_events` 表),自该功能上线起记录,更早的推进不可回溯;窗口构成条只读取消息 id/角色/token 估算元数据,不触碰正文——正文浏览走「对话」页。信封不落库是前缀缓存契约,构成条只展示最近一封的缓存分解,历史时刻定格时显示最近一封并标注;KPI 中的实时锚点、窗口行数与 token、生效水位参数同样来自 bot 进程快照(进程重启后首轮请求才会重新出现纪元键)。 +纪元看板的数据口径:主图每轮对话保留一个采样点,同轮多次模型调用按对话轮去重。锚点推进事件从功能上线后开始记录;窗口构成条展示消息编号、角色和 token 估算,正文在“对话”页浏览。信封构成展示最近一封的缓存分解,定格历史时刻也会标注这是最近一封。实时锚点、窗口行数、token 和生效水位来自 Bot 运行快照;重启后首次请求会重新建立纪元看板数据。 -诊断页的“探活 Provider”按钮会对所有已配置 provider 各发一次 max_tokens=1 的真实请求,可能产生 provider 计费,用于管理员主动全量巡检;群内 `/llm reload` 的重载后验证只探活当前会话实际生效的 provider/model。 +诊断页的“探活 Provider”按钮会对已发布配置中启用的 provider 默认模型分别提交探测任务,每个目标最多发一次 max_tokens=1 的真实请求,可能产生 provider 计费,用于管理员主动巡检。页面展示排队和执行状态,通过 Bot 的最终任务结果确认完成;提交响应丢失时复用任务请求标识查询,未确认结果不自动重发模型请求。样本请求使用同一任务机制,完成后展示模型正文、用量、耗时和 Trace。群内 `/llm reload` 的重载后验证只探活当前会话实际生效的 provider/model。 LLM Trace 以一次 HTTP 尝试为一条调用记录,并把同一轮 Agent Tool Loop 内的调用归入一个明显分组。请求正文是交给 HTTP 客户端的 UTF-8 JSON 序列化文本,详情页可在格式化 JSON 和传输原文之间切换;普通响应保留解析前的服务端 JSON 文本;流式响应完整消费 SSE 后,按 OpenAI、Claude、Gemini 或 OpenAI Responses 协议重建为一份接近非流式结构的完整响应对象。详情页默认展示组合 JSON,也允许管理员切换到 SSE 传输原文。主列表和实时更新只传输调用元数据,选择记录后才读取请求正文、响应正文和 Header。故障切换、重试和 Tool Loop 后续轮次分别保留 HTTP 明细,并通过 Agent Loop ID 与组内序号关联。 该页面面向最高权限管理员,正文和 Header 不做脱敏。页面会明确提示其中可能包含 API 凭证、系统提示和用户内容;当 MCP 工具图片实际发送给 provider 时,原始请求正文还会包含重新编码后的图片 base64,且记录体积会增大。建议只在排障期间开启采集。记录保存在 `data/llm_trace.db`,默认保留 14 天。 -从使用 JSONL Trace 的版本升级时,`data/logs/quickquip_trace_YYYY-MM-DD.jsonl` 历史记录不会导入新的调用索引;Web Admin 访问 Trace 存储或产生新调用记录时,运行时仍会按同一 14 天保留期清理这些旧文件。 - -### Bot 执行动作队列 - -`web_api.py` 是独立进程,不能直接复用 bot 进程里的 OneBot 连接。诊断页的运行时重载、LLM 健康检查、上下文清理、唤醒参数重载、群组页的“立即生成总结/播报/周报/月报”、定时消息页的任务重注册等需要 bot 进程执行的动作,会先写入 `data/web_admin_actions.db`。bot 端定时任务 `web_admin_action_queue` 每 5 秒领取并执行队列任务,结果回写到同一数据库;诊断页“最近动作”用于查看等待、执行中、成功或失败状态,并可通过“清空历史”按钮清除已结束(成功/失败)的动作记录,排队中与执行中的动作不受影响。动作队列数据库启用 WAL;若 bot 在领取任务后退出,后续轮询会将超时的 `running` 动作标记为失败,避免任务永久挂起。 +从使用 JSONL Trace 的版本升级时,`data/logs/quickquip_trace_YYYY-MM-DD.jsonl` 历史记录不会导入新的调用索引;Web Admin 访问 Trace 存储或产生新调用记录时,运行时按同一 14 天保留期清理这些旧文件。 -普通配置文件和群级开关仍走文件持久化路径。bot 端 `web_admin_state_sync` 每 30 秒检测 `rule_switch.json`、`config/awakening.toml`、每日总结/播报群组文件、无聊唤醒群组文件的修改并重载。唤醒标签页和配置页保存 `config/awakening.toml` 后也会主动入队一次 `awakening_reload`。 +Trace 存储初始化时自动补建状态与开始时间的组合索引,供超过一小时的待完成记录检查使用。已有数据库首次补建需读取历史记录一次,耗时取决于数据规模;后续启动复用索引,检查可直接从索引取得匹配记录,避免扫描正文。 ---- +### Bot 执行动作队列 -## 与项目解耦情况 +Web 与 Bot 分别运行;后台把需要连接 QQ 或读取 Bot 运行状态的动作交给机器人执行。诊断页的运行时重载、LLM 健康检查、上下文清理、唤醒参数重载、群组页的“立即生成总结/播报/周报/月报”、定时消息页的任务重注册等需要 bot 进程执行的动作,会先写入 `data/web_admin_actions.db`。bot 端定时任务 `web_admin_action_queue` 每 5 秒领取并执行队列任务,结果回写到同一数据库;诊断页“最近动作”用于查看等待、执行中、成功或失败状态,并可通过“清空历史”按钮清除已结束(成功/失败)的动作记录,排队中与执行中的动作不受影响。动作队列数据库启用 WAL;若 bot 在领取任务后退出,后续轮询会将超时的 `running` 动作标记为失败,避免任务永久挂起。 -当前实现保持了较高解耦度: +LLM 配置控制任务存于 `data/llm_config.db`,与通用动作队列独立;任务状态为 queued/running/succeeded/failed/cancelled,只有排队任务可取消。Bot 每 5 秒领取任务并回报实际配置与组件状态。运行状态由 Bot 回报;重启加载已发布配置,草稿通过单独应用生效。 -- 不依赖任何个人网站用户体系 -- 不依赖外部 OAuth / SSO -- 不依赖前端构建时注入站点私有 token -- 不依赖额外数据库服务 +其他配置文件和群级开关继续走各自的文件持久化路径。bot 端 `web_admin_state_sync` 每 30 秒检测 `rule_switch.json`、`config/awakening.toml`、每日总结/播报群组文件、无聊唤醒群组文件的修改并重载。唤醒标签页和配置页保存 `config/awakening.toml` 后也会主动入队一次 `awakening_reload`。 -其他使用者克隆仓库后,只需设置自己的 `WEB_ADMIN_PASSWORD`,构建前端并启动 `python web_api.py`,即可使用同一套机制。 +--- 记录正文结构升级、历史预览和回填见 [记录身份迁移与验收](record-identities.md)。 diff --git a/skills.example/self-docs/references/docs-dev-architecture.md b/skills.example/self-docs/references/docs-dev-architecture.md index 09d094b2..5df7fea1 100644 --- a/skills.example/self-docs/references/docs-dev-architecture.md +++ b/skills.example/self-docs/references/docs-dev-architecture.md @@ -2,13 +2,13 @@ # QuickQuip 项目架构与结构 -本文档记录整个仓库的目录与文件用途,以及“分发层”与“自用层”的划分原则。 +本文面向开发者和部署维护者,说明仓库目录、代码分层、依赖方向,以及公开分发内容和私有部署资产的存储位置。 开发文档的公共/私有边界与职责索引见 [`README.md`](README.md);源码结构规则见 [`style.md`](style.md)。 --- -## 核心概念:分发层 vs 自用层 +## 核心概念:分发层与自用层 | 层 | 含义 | 存储位置 | |---|---|---| @@ -32,9 +32,9 @@ QuickQuip 代码组织为三层结构: -1. **`src/quickquip/chat|common|llm|games|sts|tieba|search|generation`** — 框架无关的业务逻辑 +1. **业务逻辑** — `src/quickquip/` 下的 `chat`、`common`、`llm`、`games`、`sts`、`tieba`、`search` 和 `generation`,独立于框架运行 2. **`src/quickquip/adapters/nonebot/`** — NoneBot2 适配层(所有 matcher / command 注册在此) -3. **`src/plugins/`** — NoneBot2 插件发现入口,只做 re-export,不含业务逻辑 +3. **`src/plugins/`** — 通过 re-export 暴露插件对象,供 NoneBot2 发现 消息流顺序: @@ -53,11 +53,11 @@ NoneBot2 event → tz_tracker_plugin matcher 4. `text_reply_rules` — 正则彩蛋匹配(优先级 + 加权随机) 5. `context_rules` — 语境感知规则(regex_context / llm_context 判定) 6. `build_timezone_reply()` — 时区猜测 -7. STS `card_le` — “xxx了”公式,位于规则链末尾(「X了」正则快筛、规则开关、限频预检与概率掷骰均先于 `match_card_le` 的 LLM 判定;不得抢占时区等具体规则;按符号定位:`resolve_reply` 中的 `match_card_le` block) -8. `rule_switch.is_enabled()` — 每步均受群级规则开关控制 -9. `reply_probability.roll_reply()` — 概率掷骰:text/context 规则在匹配器内掷一次并打 `PROBABILITY_CHECKED` 标记(card_le 在快筛后掷并打标),其余路径(复读/接龙/游戏/时区)由 `resolve_reply` 出口按限流桶兜底掷;显式 LLM、群唤醒与无聊唤醒在各自发送路径掷,私聊按用户隔离状态。未配置概率默认必回 -10. `rate_limit.allow()` — 发送前限流检查 -11. `stats_tracker` — 消息统计与规则触发计数 +7. STS `card_le` — “xxx了”公式,位于规则链末尾。`resolve_reply` 中的 `match_card_le` 分支先执行“X了”正则快筛、规则开关、限频预检和概率掷骰,再进行 LLM 判定 + +规则回复经过群级 `rule_switch.is_enabled()` 检查,管理员可切换的规则由 `SWITCHABLE_RULES` 登记。内置游戏当前的操作与停用范围见[游戏管理](../admin/game-config.md#配置范围)。text/context 规则在匹配器内掷骰并写入 `PROBABILITY_CHECKED` 标记;card_le 在快筛后掷骰并写入同一标记。复读、接龙、游戏和时区路径由 `resolve_reply` 出口按限流桶掷骰;显式 LLM、群唤醒和无聊唤醒在各自发送路径掷骰,私聊按用户隔离状态。未配置概率时默认回复。 + +发送前由 `rate_limit.allow()` 检查限流,`stats_tracker` 记录消息统计和规则触发次数。 ### 依赖方向与组合根 @@ -118,7 +118,7 @@ QuickQuip/ ## `src/quickquip/` — 业务逻辑包 -项目采用 src layout,所有源码位于 `src/` 下。包导入路径 `quickquip.*` 保持不变。 +项目采用 src layout,源码位于 `src/` 下,业务包通过 `quickquip.*` 导入。 ``` src/quickquip/ @@ -155,8 +155,8 @@ src/quickquip/ | 文件 | 层 | 说明 | |---|---|---| -| `llm.toml.example` | 分发层(追踪) | LLM provider / runtime / tools / MCP 配置模板 | -| `llm.toml` | 自用层(gitignore) | 真实 provider 配置,含 base_url / model 等 | +| `llm.json.example` | 分发层(追踪) | LLM 数据库的 JSON 交换示例 | +| `llm.toml` | 迁移资产(gitignore) | 首次迁移输入与迁移前版本回退配置 | | `generation.toml.example` | 分发层(追踪) | 多模态产出配置模板 | | `generation.toml` | 自用层(gitignore) | 真实图片/语音/音乐 provider 配置 | | `awakening.toml.example` | 分发层(追踪) | 群聊唤醒模块配置模板 | @@ -174,7 +174,9 @@ src/quickquip/ | `personas.example/` | 分发层(追踪) | persona 配置格式示例 | | `personas/` | 自用层(gitignore) | 真实 persona 定义(含人格描述、系统提示等) | -**原则**:永远只编辑 `.toml` / `personas/`,不编辑 `.example`。`.example` 只在格式需要变更时更新。例外:`niuniu_text.toml` / `niuniu_text_safe.toml` 虽是无 `.example` 后缀的 `.toml`,但属**被追踪的分发层,勿写入私有内容**——私有文案放在未被追踪的独立文件中,通过 `games.toml` 的 `niuniu_text_path` / `niuniu_safe_text_path` 指向(与 configuration.md 一致)。 +部署者从 `.example` 复制私有配置,再编辑自己的配置文件与 `personas/`;LLM 配置通过数据库草稿管理。贡献者在格式或示例行为变化时更新公开模板。 + +`niuniu_text.toml` 和 `niuniu_text_safe.toml` 是被追踪的分发文件,内容应适合公开。私有文案放在未被追踪的独立文件中,通过 `games.toml` 的 `niuniu_text_path` 或 `niuniu_safe_text_path` 指向,字段说明见[配置参考](../admin/configuration.md)。 --- @@ -194,6 +196,7 @@ data/ ├── web_admin_actions.db # Web Admin 到 bot 进程的动作队列 ├── audit.db # Web Admin 审计日志(SQLite) ├── llm_trace.db # LLM HTTP 调用索引与完整 JSON 请求/响应文本(SQLite,保留 14 天) +├── llm_config.db # 草稿、不可变发布快照、配置任务与 Bot 心跳 ├── llm_usage.db # LLM 用量与成本统计(SQLite) ├── mcp_status.json # MCP server 装载状态快照 ├── cron_jobs.json # Cron 定时任务调度状态快照(bot 进程写,web-admin 读) @@ -232,7 +235,10 @@ docs/ │ ├── deployment.md │ ├── onebot-adapters.md │ ├── configuration.md +│ ├── llm-configuration.md +│ ├── client-fingerprints.md │ ├── game-config.md +│ ├── global-admins.md │ ├── migration-napcat-to-llbot.md │ ├── mcp-servers.md │ ├── record-identities.md @@ -247,19 +253,18 @@ docs/ ## 私有部署材料 -真实部署脚本配置、运维通知密钥、compose 运行态目录和临时分析材料均属自用层,不属于公共仓库分发内容。公共文档只记录通用配置格式和运行方式,不记录个人生产目录结构。 +真实部署脚本配置、运维通知密钥、compose 运行态目录和临时分析材料均属自用层。公共文档记录可公开使用的配置格式、模板和运行方式。 ### `prod.example/` 与 `prod/` - `prod.example/`:可公开分发的生产运维模板,包含 compose、Dockerfile、部署脚本、巡检脚本和示例通知配置。 - `prod/`:由 `prod.example/` 复制得到的真实生产运维目录,进入 `.gitignore`,可保存服务器专用脚本配置、OneBot 协议端登录态目录和运维通知密钥。 -- 本地私有工作区只用于草稿、测试沙箱、探针脚本和工作文档,不承担生产环境变量覆盖职责。 +- 本地私有工作区保存草稿、测试沙箱、探针脚本和工作文档。 ### 私有环境变量与根 `.env` 的关系 - **根 `.env`**:QuickQuip 应用唯一的涉密环境变量来源,供本地运行与 `prod/` 容器部署共同读取,必须保持 gitignore。 - **`prod/sendkey.env`**:可选运维通知密钥,仅由巡检脚本读取,不被 QuickQuip 应用加载。 -- 本地私有工作区只用于草稿、测试沙箱、探针脚本和工作文档。 ### `llm_about` 部署路径 @@ -267,7 +272,7 @@ docs/ - 宿主机 `llm_about/` → 容器内 `/app/llm_about/` -历史私有资料路径已弃用,不应再被 compose 挂载或由部署脚本读写。 +Compose 挂载与部署脚本使用上述统一路径。 --- @@ -276,8 +281,9 @@ docs/ | 路径 | 原因 | |---|---| | `.env`, `.env.*` | 含真实密钥(`.env.example` 除外) | -| `config/llm.toml` | 含真实 provider 配置 | -| `config/llm.*.local.toml` | 同上 | +| `data/llm_config.db` | 运行配置、发布快照与控制状态 | +| `config/llm.toml` | 首次迁移输入与迁移前版本回退资产 | +| `config/llm.*.local.toml` | 旧部署的本地覆盖资产 | | `config/generation.toml` | 含真实多模态 provider 配置 | | `config/awakening.toml` | 含真实唤醒阈值、兴趣话题和群覆盖 | | `config/sensitive_words.toml` | 含部署者填充的敏感词词表 | diff --git a/skills.example/self-docs/references/docs-dev-branching.md b/skills.example/self-docs/references/docs-dev-branching.md index 3e58e0c8..cc0f5e36 100644 --- a/skills.example/self-docs/references/docs-dev-branching.md +++ b/skills.example/self-docs/references/docs-dev-branching.md @@ -2,14 +2,14 @@ # QuickQuip 开发工作流与发布流程 -本项目采用精简 GitFlow:`dev` 是日常集成分支,`main` 是发布专线。源码结构规则见 [`style.md`](style.md),测试纪律见 [`testing.md`](testing.md),架构与领域所有权见 [`architecture.md`](architecture.md),主题版本、累积更新与开发版本约定见 [`versioning.md`](versioning.md)。 +本项目采用精简 GitFlow:`dev` 是日常集成分支,`main` 是发布专线。源码结构规则见 [`style.md`](style.md),测试纪律见 [`testing.md`](testing.md),公开文档规范见 [`documenting.md`](documenting.md),架构与领域所有权见 [`architecture.md`](architecture.md),版本约定见 [`versioning.md`](versioning.md)。 ## 硬规则 - 只有收到明确指令时才 commit、push、开 PR、合并或打 tag。 - 一个分支或 PR 只承载一个主要意图;大变更先拆分。 - 行为、配置、协议、部署契约或用户文档变化时,同一变更更新拥有该事实的文档;`feat`、`fix`、`refactor` 依项目约定维护本地 changelog 草稿。 -- 不提交 secret、`.env`、`data/`、真实 `prod/`、本机工作材料或生成物。 +- 不提交 secret、`.env`、`data/`、真实 `prod/`、本机工作材料或临时构建产物。受追踪的文档副本与接口契约按生成工具同步。 - 使用 Conventional Commits;PR 保留 merge 历史,不 squash。 ## 分支模型 @@ -42,7 +42,7 @@ hotfix/* (仅生产阻断) ────────────────→ m | **Standard PR** | 中型或高风险域 | 从 `dev` 建短分支 | 独立 CR + Bot Review,并行 | 无未解决 Blocking/Should-fix 后请求人工合并 | | **Huge PR** | 大型、跨模块、高风险 | 短分支;必要时拆多个 PR | 全量 Tier 2 Deep-CR;每个拆分 PR 保持 Standard | Deep-CR 结论收口后请求人工合并 | | **Hot-Fix** | `main` 或 tag 的阻断回归 | 从 `main` | 按风险,非平凡变更至少 Tier 1 | PR 到 `main`,再回灌 `dev` | -| **Release** | 公开发布 | `dev → main`;必要时 `release/*` | 独立 CR + 发行物/消费者验收 | 合并 `main` 后打 tag | +| **Release** | 公开发布 | `dev → main`;必要时 `release/*` | 独立发布专项 CR + 常规检查;特殊里程碑另定专项清单 | 人工批准合并;tag 单独批准 | ### Develop direct @@ -67,7 +67,7 @@ hotfix/* (仅生产阻断) ────────────────→ m ### Huge PR -触发条件:大型、跨模块、高风险改动,以及面向 `main`、tag 或 release 准备的改动。高风险路径映射和数值门槛以 `scripts/check/deep-cr-trigger.sh ` 的输出为唯一权威;它无法解析 base 时 fail-safe 地要求 Deep-CR,而不会假定变更低风险。 +触发条件:大型、跨模块、高风险的实现改动。高风险路径映射和数值门槛以 `scripts/check/deep-cr-trigger.sh ` 的输出为唯一权威;它无法解析 base 时 fail-safe 地要求 Deep-CR,而不会假定变更低风险。发布期间新增的功能性修复按其自身差异分级;Release 累计交付范围的评审采用下方发布专项 CR 规则。 开始前在本地私有工作区编写专题计划,写明目标与验收条件、涉及子系统、风险与失败模式,以及拆分方案。必要时把实现拆为多个 Standard PR,每个 PR 只保留一个主要意图。 @@ -87,7 +87,23 @@ hotfix/* (仅生产阻断) ────────────────→ m ### Release -Release 在 `dev` 上冻结版本、CHANGELOG 与发行范围;若需要额外收束,可使用 `release/v-`。发布评审至少达到 Standard,满足 Deep-CR 触发条件时按 Huge 执行,并完成与风险相称的 Windows/Docker/Linux 消费者验收。详情见下方“发布生命周期”。 +Release 在 `dev` 上冻结版本、CHANGELOG 与发行范围;若需要额外收束,可使用 `release/v-`。冻结记录包含起始 SHA、目标分支基线和最终候选 SHA。后续开发改动进入发行范围须明确选择并更新记录。 + +普通累积版本执行一轮独立发布专项 CR 和常规完整检查。新一代 Minor、Minecraft 版本号致敬等特殊里程碑,由维护者在冻结时确定额外审查与消费者验收清单。普通 Release 可直接准备正式版本;RC、候选构建入口与平台专项验收按明确需要安排。 + +#### 发布专项 CR + +由未参与发布准备的 reviewer 独立检查以下内容,并读取相关实际代码、测试与契约: + +1. **交付与证据**:以上一发行 tag/目标分支到候选的累计范围建立变更摘要,关联已有 PR 的评审和验证,核对遗漏、未解决发现与延期事项。 +2. **发布一致性**:检查发布准备新增的差异,以及版本、CHANGELOG、升级说明、公开文档、配置模板、启动/打包/部署入口之间的一致性。 +3. **集成风险**:沿本期重要的跨 PR 接口核对状态生命周期、配置生效、数据迁移与回退等契约,检查现有验证是否覆盖实际边界。 + +复审范围由已有交付证据和具体问题确定。证据缺口、契约冲突或可复现问题触发对应模块的扩展审查。累计 Diff 的规模及其 Deep-CR 脚本输出用于定位风险,普通 Release 按上述专项范围审查;新增实现改动按 Standard/Huge 规则执行,以该改动的起始 SHA 为分级基线。 + +报告注明累计范围、发布准备范围、重点检查的边界及证据链接,使用 Blocking、Should-fix、Nits、Verified claims、Not verified。Blocking 必须修复;Should-fix 修复或在 PR 记录延期理由。普通 Release 保留完整自动化检查,平台与真实渠道未验证内容如实报告。Tag 后由现有发行流水线构建和 smoke,并核对实际产物。 + +Release PR 合并和最终 tag 分别需要维护者专门人工审核批准,并各自记录获批的 SHA。候选 SHA 改变后更新审查与验证,重新提交审核。详情见下方“发布生命周期”。 ## 日常迭代与验证 @@ -103,7 +119,8 @@ Release 在 `dev` 上冻结版本、CHANGELOG 与发行范围;若需要额外 | 小型 Python 改动 | `.venv/bin/ruff check .` 与相关 pytest | | 小型前端改动 | `pnpm --dir frontend type-check` 与必要的 build/组件检查 | | LLM/MCP、持久化、消息管线、Web Admin、配置或部署 | Ruff、相关 pytest、前端 type-check(触及前端时)、示例配置校验与实际边界 smoke | -| release / `main` 候选 | 完整 pytest、Ruff、示例配置校验、前端 type-check/build、发行 workflow 产物验证,以及可行的真实消费者验收 | +| Release 候选 | 完整 pytest、Ruff、示例配置校验、self-docs 与 ID 检查、前端测试/type-check/build、发布说明提取检查;特殊里程碑按冻结清单补验 | +| Tag 后发行 | 发行 workflow 的构建与 smoke;tag、版本、ZIP、镜像 revision/digest 与 Release notes 一致性核对 | 常用命令: @@ -115,12 +132,13 @@ pnpm --dir frontend type-check pnpm --dir frontend build ``` -无法执行的网络、Playwright、真实 provider 或平台验证必须在 PR/交接中明确报告为未验证,而不能作为通过。 +在 PR 或交接中逐项记录验证对象与结果;无法执行的网络、Playwright、真实 provider 或平台检查标记为未验证。 ## 两级代码评审 - **Tier 1(默认)**:对所有 Standard PR 和非平凡 Hot-Fix 运行一轮独立、只读的 CR。`.claude/agents/quickquip-cr-reviewer.md` 是可复用的 reviewer 定义;任何未参与实现的合格审查者均可执行相同契约。 - **Tier 2(Deep-CR)**:仅用于 Huge PR。五个领域 finder 独立寻找候选,再由其他审查者复核证据与置信度。`scripts/check/deep-cr-trigger.sh` 只负责确定是否达到门槛;它不替代实际审查。 +- **Release 专项 CR**:执行“发布专项 CR”中的一轮独立检查,范围包括发布准备差异和选定集成边界;特殊里程碑按维护者确定的清单扩展。 评审输出统一使用:Blocking(合并前修复)、Should-fix(除非记录延后理由否则修复)、Nits(可选)和 Verified claims(可记录于 PR/merge notes)。 @@ -134,7 +152,7 @@ Bot Review(KHPilot,PR 侧自动评审)的机制事实与两轨汇合纪律 - **评审进行中 PR head 移动会立即中断当轮评审**;中断后一般不补审,确有必要按下条请求复审。双轨编排因此固定为:**先开 PR(触发 Bot)、再启动本地独立 CR**——本地 CR 完工时 Bot 结论通常恰好到达,两轨正好汇合。 - 评审耗时随 diff 规模线性:小型 PR 约 3–10 分钟,百文件级大 diff 可近 1 小时。 - `@khpilot` 评论触发的是**对话式回应**(摘要回复),与 opened 触发的结构化评审(check run + 四分类 findings)是两条管线。请求复审仅在 Bot 结论对合并决策确有必要时进行,评论中给出新 head SHA 与验证结果,避免主执行 Agent 与 Bot 陷入循环。 -- **沉默不代表 approval**;Bot review 也不是 CI check 或合并门禁,CI 结果仍以 GitHub Checks 为准。 +- Bot Review 的状态以实际评审结论为准;没有结论时记录为待确认。CI 结果以 GitHub Checks 为准,人工合并按本文件的授权与评审要求执行。 ### 等待编排 @@ -174,8 +192,8 @@ fi 1. 按 [`versioning.md`](versioning.md) 确定本次目标版本,在 `dev` 或用于额外收束的 `release/*` 冻结候选 SHA、`pyproject.toml` 版本、CHANGELOG、公开文档和配置模板;需要预发布验收时使用 `X.Y.Z-rc.N`,正式发布前定为 `X.Y.Z`。 2. 汇总本地 changelog 草稿与已合并历史,将 `Unreleased` 形成新版本段并更新比较链接;确认草稿在 release 成功后再清理。 -3. 为 `dev → main`(使用发布准备分支时为 `release/* → main`)开 release PR,标题为 `release: vX.Y.Z — <摘要>`;完成分级要求的评审和验证。 -4. 合并 release PR 后,在 `main` 的已接受提交上创建并推送 `vX.Y.Z` tag。 +3. 为 `dev → main`(使用发布准备分支时为 `release/* → main`)开 release PR,标题为 `release: vX.Y.Z — <摘要>`;完成发布专项 CR、常规检查和已确定的专项清单,提交最终候选 SHA 供维护者人工审核。收到该候选的合并批准后,保留 merge 历史合并。 +4. 合并后核对 `main` 上的已接受提交及 CI,把准确 tag 名和目标 SHA 提交维护者单独审核。收到 tag 批准后创建并推送 `vX.Y.Z`。 5. tag 触发 `release.yml`:完整测试、Windows 懒人包、Docker 镜像与 GitHub Release。核对 tag、版本、ZIP、镜像 revision/digest 和 Release notes 一致。 6. 把 `main` 回灌 `dev`:若能快进则 `git merge --ff-only main`;否则开 `chore/back-merge-vX.Y.Z` PR。确认 post-merge CI 后清理已发布的本地草稿与短分支。 7. back-merge 完成后核对 dev 的下一目标版本:常规发布后默认进入下一个 Patch 的 `-dev.0`,确定新主题时进入下一 Minor;已开始后续开发时保留其有效目标。需要调整时更新 `pyproject.toml`,按授权以 `chore:` 提交、推送。Hotfix 占用目标版本时,按 [`versioning.md`](versioning.md) 重新选择目标,确保后续开发使用开发版本标识。 diff --git a/skills.example/self-docs/references/docs-dev-documenting.md b/skills.example/self-docs/references/docs-dev-documenting.md new file mode 100644 index 00000000..89180e73 --- /dev/null +++ b/skills.example/self-docs/references/docs-dev-documenting.md @@ -0,0 +1,185 @@ + + +# QuickQuip 公开文档规范 + +本文件是 QuickQuip 的“文档的文档”,定义全仓公开文档的写作、维护和审查要求。所有人类贡献者与 AI 协作者撰写、修改、生成或审查公开文档时,均须遵循本规范。 + +适用内容包括根目录项目文档、`docs/`、生产模板的使用说明、公开协作指令、Issue/PR 模板、随包 Skill 文档与生成副本。配置示例中的解释文字和面向使用者的脚本说明同样适用。 + +文档的质量目标是让目标读者理解事实、做出决定并完成操作。内容取舍、结构和验证都围绕这个目标展开。 + +## 文档职责与权威来源 + +每份文档应有明确读者、主要任务和事实来源。按以下职责组织内容: + +| 文档位置 | 主要职责 | +|---|---| +| 根目录 `README.md` | 项目入口、功能概览、快速开始与导航 | +| `docs/user/` | 群友能使用的功能、命令、预期结果与常见问题 | +| `docs/admin/` | 安装、配置、升级、运维、备份恢复与排障 | +| `docs/dev/` | 开发契约、实现原理、工程规范与交付流程 | +| `CHANGELOG.md` | 各版本用户可感知的变化及升级事项 | +| `ROADMAP.md` | 已确定方向、待调研问题与候选计划 | +| 公开模板与示例目录 | 随模板使用的前提、字段说明和操作指引 | + +同一事实由最贴近其读者与职责的一份文档维护,其他页面链接到该权威来源。开发原理、管理员操作和用户体验可以分别说明各自所需的信息;易变字段表、维护 SQL 和长命令清单统一维护。 + +新建独立文件须有稳定且独立的职责。新增页面在 [`../index.md`](../index.md) 与所属目录索引登记;调整标题、文件名或位置时同步维护链接和锚点。 + +## 读者与内容组织 + +- 开篇说明文档解决什么问题、适合谁阅读;需要权限、版本或部署形态时,给出具体前提。 +- 先提供结论、用法或操作入口,再解释读者需要的原因和机制。标题围绕任务命名。 +- 用户文档使用熟悉的词语与具体例子。管理员文档交代执行位置、配置来源和生效方式。开发文档解释契约与实现依据。 +- 一段发展一个主要意思,句子按因果或操作顺序衔接。平行信息用列表,映射和比较用表格,操作步骤用有序列表。 +- 篇幅由任务复杂度决定。删除空泛开场、重复总结、同义堆叠和与当前任务无关的实现细节。 +- 教程、项目介绍和彩蛋文档可按自身定位使用亲切或幽默的语气;事实、命令和操作条件保持准确。 + +## 当前事实、计划与证据 + +现行说明直接描述目标版本的完整行为。演变过程、代际差异和事故经过放在 CHANGELOG、迁移说明或明确标识的历史材料中。正文保持连贯成文,避免“原先……现在……”或“不是……而是……”式修补痕迹。 + +- 项目行为以实际实现、字段校验、公开模板和既定契约为依据。核对保存、发布、加载、执行与失败发生的阶段。 +- 命令名、参数、配置键、默认值、页面导航和错误名称采用实际拼写。涉及全局、渠道、模型、群或私聊时写明作用范围。 +- 已实现能力写入现行说明;候选设计与调研事项进入 ROADMAP,并给出当前需要解决的问题。已完成的计划同步更新状态。 +- 外部服务、依赖或平台信息注明适用版本;易变信息给出核对日期和直接支持结论的官方来源。来源不足的判断写明推断依据,待确认的事实先核实再写成确定结论。 +- 验证记录写清对象、版本/提交、方法和结果。使用与实际证据相符的动词,例如“语法校验通过”“隔离数据库执行通过”“Windows 实机启动通过”。 + +## 文风:面向读者的确定表达 + +优先写清主体、动作、条件和结果,使用具体、直接的陈述。每句限制或保留都应能回答:**它帮助读者做出什么决定,或执行什么操作?** + +### 模型协作者重点自查 + +本节的文风分析重点面向 GPT 系模型及其他可能运行在 Codex 中的模型;其他模型同样应据此自查,有则改之,无则加勉。所有贡献者都按本节要求审查自己的文字。 + +“人类可读性差”涵盖读者适配、词句表达、逻辑连接和信息取舍等具体问题。本节结合写作与文档研究,将这些问题转化为项目审查要求;以目标读者能否理解事实并完成任务判断修订效果。 + +#### 预防性免责语调 + +**预防性免责语调**是围绕一个事实或成果,连续添加限缩、否定推论和待办边界,使作者的责任保护占据超过读者实际决策需要的篇幅。“免责声明”描述其表达形式,“过度边界”描述其组织信息的方式。 + +典型组织顺序为:陈述成果 → 限缩意义 → 列举无法推出的结论 → 补充尚待完成的事项。例如: + +> 这只代表配置检查通过,不能认为部署成功,也不证明上游可用,真实渠道仍需验证。 + +这类写法容易预设读者会过度解读,把作者的责任保护放在信息表达之前,并反复制造“还有欠账”的感觉。具体操作限制与泛化保留混在一起,会削弱真正重要的条件。 + +审查时按以下特征定位问题: + +| 特征 | 常见句式 | 修订方向 | +|---|---|---| +| 免责声明 | “这只代表……”“也不证明……” | 准确命名已完成的验证及结果 | +| 过度边界 | 连续罗列当前任务之外的场景、例外和验证 | 保留影响当前读者选择的条件,其他内容归入对应任务文档 | +| 欠账式收尾 | 段落反复以“仍需……”“尚待……”结束 | 待办集中记录为有具体问题和跟踪入口的计划 | +| 预设误解 | “不能认为……”“也不意味着……” | 直接说明当前行为、适用条件或下一步操作 | + +修订应调整信息职责与结构,保持事实准确。单纯替换词语会保留原有的免责组织方式。以下例子展示面向读者的表达: + +| 场景 | 写法 | +|---|---| +| 配置检查结果 | “配置校验通过。部署健康检查由部署脚本执行;渠道连通性在模型探测入口查看。” | +| 保存配置 | “保存会更新草稿。点击应用后,等待 Bot 加载状态显示目标版本。” | +| 功能启用条件 | “工具调用默认关闭。在工具与技能页面开启,保存草稿后应用。” | +| 后续产品计划 | “Web 已支持报告配置编辑。运行反馈改进记录在 ROADMAP 的报告配置体验条目。” | +| 明确失败条件 | “重复的 MCP ID 会使配置保存校验失败,请为每个服务设置唯一 ID。” | + +“不”“也不”“仍”“仅”等词用于搜索候选句。审查依据是句子的职责、读者收益和出现密度;保留表达具体规则、现实状态与操作条件的必要否定。例如“重启不会应用草稿”直接解释生效方式,“删除前先迁移群绑定”直接指导操作。 + +#### 读者适配与实现细节 + +**读者适配失败**表现为文档直接复述作者理解实现的过程,读者需要自行从函数、对象和状态转换中提取用法。**实现细节侵入正文**表现为内部标识符或源码片段承担了本应由自然语言完成的解释。 + +- 先写读者需要理解的行为与完成的操作。管理员文档说明配置入口、作用范围和生效方式;开发文档按理解与维护契约的需要解释实现。 +- 自然语言承担解释,标识符帮助准确定位对象。用户需要输入的命令、配置键、错误码和相关文件路径保留实际名称;内部函数名、类名和调用链按读者任务选择。 +- 代码块须服务于明确任务,选取足够完成该任务的示例,并说明执行位置、替换项和预期结果。介绍功能时用文字解释行为;实现分析引用关键片段并解释其作用。 +- 先选择必要的内容,再按代码格式规则排版。正文的条件、原因、先后关系和读者操作写成完整句子。 + +例如,管理员需要知道“保存会生成草稿,应用后等待机器人加载目标版本”。支撑这一行为的内部对象与方法由开发契约解释。Google 的[文档实践](https://abseil.io/resources/swe-book/html/ch10.html)按读者区分使用说明与实现细节,[代码字体规范](https://developers.google.com/style/code-in-text)说明准确标记代码实体的用途;[GPT-4 文档读者适配研究](https://link.springer.com/chapter/10.1007/978-3-031-72781-8_11)也观察到面向管理者调整内容后理解与可读性的改善。 + +#### 常见表达与内容失真 + +| 问题 | 审查时关注的表现 | 修订要求 | +|---|---|---| +| 名词化与抽象表达堆积 | 一句话塞入多层“管理/机制/状态/一致性”等抽象概念,主体和动作难以辨认 | 写清谁在什么条件下做什么,例如“确认机器人已加载刚发布的配置” | +| 华丽但空泛的评价 | “全面提升稳定性”“构建健壮闭环”等评价占据篇幅,实际变化模糊 | 写清改变后的行为、触发条件与读者收益,以具体事实支撑评价 | +| 机械的文章模板 | 小任务也重复预告、固定分节、阶段标记和总结;不同文档使用同一套话术 | 按任务需要组织结构,保留有导航或解释作用的段落 | +| 重复与低信息增量 | 开篇、正文、提示框和结尾反复表达同一事实 | 每段增加读者需要的信息,合并同义复述,其他入口链接到权威说明 | +| 排版替代论述 | 标题、加粗、列表或斜杠串起关键词,句子之间的关系靠读者拼接 | 写明条件、原因与操作顺序;列表承载真正平行或有序的信息 | +| 措辞流畅,关键条件缺失 | 功能概括完整,默认值、权限、前提、失败行为或生效阶段被省略 | 对照实际契约补齐影响读者决定与操作的条件 | +| 按熟悉模式补写行为 | 根据函数名、惯常设计或同类产品,补出貌似合理的功能说明 | 逐项依据实际实现、公开模板与既定契约写明行为 | +| 连续改写造成语义漂移 | 润色、压缩或重组时遗漏条件、改动事实、混淆状态或改变引用 | 对照原文与权威来源核对受影响内容;保留正确事实及其条件,同步关联说明与引用 | + +这些审查项的研究依据包括: + +- [语体研究:Do LLMs write like humans?](https://arxiv.org/html/2410.16107):GPT-4o 与 Llama 3 的英语语料体现出名词偏重、固定词汇偏好及文体适配问题。 +- [修辞研究:Prompt Sensitivity in AI Writing](https://onlinelibrary.wiley.com/doi/full/10.1111/modl.70089):GPT 英语议论文中的目标宣告与阶段标记具有模板倾向,提示方式会影响修辞表现。 +- [质量分类研究:Measuring AI “Slop” in Text](https://arxiv.org/html/2509.19163v1):分别考察信息密度、相关性、重复、模板化、连贯性、冗长和词语复杂度,支持按具体问题审查文字。 +- [代码文档研究:Using Large Language Models to Document Code](https://arxiv.org/html/2408.14007v1):GPT-3.5 生成 Javadoc 的实际案例包含关键条件遗漏与行为误述。 +- [文档编辑研究:LLMs Corrupt Your Documents When You Delegate](https://arxiv.org/html/2604.15597v1):跨模型、跨领域的多轮编辑实验观察到内容损失与失真,全局重组任务更容易出现问题。 + +### 限制、警告与后续事项 + +- 把适用条件放在相关步骤前,把具体失败原因放在排障位置,把已知问题放在对应版本或功能说明中。 +- 警告说明触发条件、具体后果与处理动作。数据删除、覆盖、费用、权限和兼容性问题要让读者在执行前看见。 +- 验证覆盖与尚待确认的事项集中写在交付记录或专门的验证段落,注明具体对象及下一步;面向使用者的文档保留影响使用决策的事实。 +- 同一限制在权威来源完整说明,其他入口用必要提示与链接引导。正文避免每段追加通用风险声明。 +- 使用清楚、平实的措辞。避免营销式评价、虚构的复合术语、空泛的“值得注意”“重要的是”和结尾口号。 + +## 操作文档与排障 + +一个操作流程应能让目标读者从前提走到可观察的结果: + +1. 给出所需权限、运行状态、工具和输入,以及执行目录、Shell 或界面入口。 +2. 按真实依赖顺序组织步骤。新安装从目录或文件创建开始;迁移涉及初始化时,安排可实际访问的入口。 +3. 写明操作修改的对象、保存位置及生效方式,区分保存草稿、应用、重新加载与重启。 +4. 给出成功时可观察的输出或状态,并说明有代表性的失败原因与处理动作。 +5. 涉及数据维护或不可逆操作时,说明停服与备份条件、实际目标、改变和保留的数据、恢复入口。 + +平台与部署形态的差异就近说明。Windows、源码安装和容器示例使用各自实际可用的路径和工具。Shell、容器工作目录和主机文件路径标识清楚。 + +排障按“现象 → 原因/检查方法 → 处理动作”组织。页面错误、配置校验失败、运行时装载失败和远端调用失败分别解释。执行会产生真实请求或费用时,在操作入口说明。 + +## 配置、命令与示例 + +- 示例符合当前解析器、字段层级、类型、引用约束与默认策略。解释继承值、空值、关闭值和零值的具体语义。 +- 完整配置、结构片段和伪代码明确标识。片段说明合入位置及需要保留的配置;占位符说明替换方式。 +- 命令应可复制执行,代码块内放可用语法。交互提示符、预期输出和解释文字单独呈现;示例内注释须符合对应语言。 +- SQL、删除与覆盖命令标明目标和前提。验证有副作用的示例时使用隔离环境或合成数据,检查需要保留的数据。 +- 默认值和长字段表在权威来源维护;其他文档用链接和与当前任务直接相关的小例子说明。 +- 新增示例优先使用现有解析器、校验器或工具验证。语法、引用、执行结果和平台可用性按实际完成的验证分别记录。 + +## 格式、术语与公开信息 + +- 文本统一使用 UTF-8 与 LF。Markdown 段落和列表项保持自然换行;按结构或语义需要换行。 +- 中文散文使用弯引号“”与‘’。代码、命令和配置采用语法所需的 ASCII 引号,保证复制执行。 +- 配置键、命令、文件路径、错误码与标识符用行内代码。标题按层级组织,表格用于容易对照的信息,代码块注明语言。 +- 同一概念使用一致术语。首次出现的缩写在需要时解释,界面标签采用实际名称;代码标识符保持原样。 +- 仓库文件使用相对链接,外部引用链接到直接支持事实的页面。图片与图表提供说明;图示、截图与界面变更同步维护。 +- 公开内容使用可分发的路径、模板与合成标识。真实账号、群号、token、登录态、本机绝对路径和私有拓扑留在受控环境。 +- 工作草稿与执行记录归入本地私有工作区。公共说明通过仓库分发内容即可理解,生产运维示例来自 `prod.example/`,应用凭据来源按项目约定使用根目录 `.env`。 + +## 维护与审查 + +行为、配置、协议、命令、版本或路径发生变化时,在同一变更中更新拥有该事实的文档、例子、相关入口与生成副本。历史材料保留当时事实并注明历史用途,当前使用入口指向现行说明。 + +修改 self-docs 同步源后,运行仓库提供的生成工具,并将权威文档和副本放在同一变更中: + +```bash +python scripts/ci/sync_self_docs_references.py +python scripts/ci/sync_self_docs_references.py --check +``` + +同步源的权威名单见 [`../../scripts/ci/sync_self_docs_references.py`](../../scripts/ci/sync_self_docs_references.py)。生成副本通过工具维护。 + +文档交付前检查: + +- 目标读者能否找到入口、理解条件并完成任务? +- 当前行为、计划和历史事实是否各归其位,权威来源是否明确? +- 字段、默认值、命令、权限、页面名称及生效顺序是否与实际行为一致? +- 示例是否经过适当验证,链接、锚点、编码和生成副本是否一致? +- 文风是否适合读者,解释是否清楚,是否出现实现细节侵入、抽象堆叠、重复、模板化或排版替代论述?限制和警告是否有具体用途,是否出现预防性免责、过度边界或修补痕迹? +- 改写是否保留了正确事实、关键条件与生效阶段,关联引用是否同步? +- 公开内容是否符合路径、凭据与隐私要求? + +评审指出具体段落、读者会遇到的问题及修订办法。关键词搜索用于定位候选,句子功能与事实依据决定结论。验证和评审安排遵循 [`branching.md`](branching.md),代码与测试规范分别见 [`style.md`](style.md) 和 [`testing.md`](testing.md)。 diff --git a/skills.example/self-docs/references/docs-dev-game-framework.md b/skills.example/self-docs/references/docs-dev-game-framework.md index f3dc1232..52281052 100644 --- a/skills.example/self-docs/references/docs-dev-game-framework.md +++ b/skills.example/self-docs/references/docs-dev-game-framework.md @@ -2,7 +2,7 @@ # 游戏框架开发者指南 -本文档介绍 QuickQuip 游戏系统的架构、扩展接口和开发约定。 +本文面向已完成[开发环境搭建](../../CONTRIBUTING.md)、能阅读 Python 类和类型注解的贡献者,介绍如何将自己实现的游戏接入 QuickQuip 的消息管线、配置和金币系统。完整的 Session 型游戏可参考[数字炸弹](../../src/quickquip/games/number_bomb.py),带金币结算的实例见 [21 点](../../src/quickquip/games/blackjack.py)。 --- @@ -35,10 +35,11 @@ src/quickquip/games/ ### Session 型游戏:BaseGame -适用于有明确开始/结束的一局游戏。继承 `BaseGame`,实现 4 个方法: +适用于有明确开始和结束的一局游戏。继承 `BaseGame`,提供名称,并实现开始、结束、消息处理和活动状态查询;`aliases` 可按需覆盖。以下是接口骨架,省略了各方法的游戏逻辑: ```python from quickquip.games.registry import BaseGame, GameResult +from typing import Optional class MyGame(BaseGame): @property @@ -66,15 +67,17 @@ class MyGame(BaseGame): ... ``` -**注册**:在 `src/quickquip/app/message_pipeline.py` 中注册并注入依赖: +在 `src/quickquip/app/message_pipeline.py` 中注册实例。若游戏构造函数接收金币依赖,可按以下片段注入: ```python game_registry.register(MyGame(economy=game_economy)) ``` -**GameResult 字段**: +消息处理返回 `GameResult`。注册表根据 `finished` 清理群与游戏的关联,并把回复、艾特、限流桶和统计规则交给消息管线。字段定义如下: ```python +from dataclasses import dataclass + @dataclass class GameResult: reply: str # 回复文本 @@ -84,13 +87,18 @@ class GameResult: rule_name: str = "game_interaction" # 统计用规则名 ``` -**Session 管理**:每个游戏自己维护 `OrderedDict[str, Session]`,key 为 `group_id`。GameRegistry 只管理“哪个群在玩哪个游戏”的映射。 +每个游戏自己维护以 `group_id` 为键的会话状态,现有实现使用 `OrderedDict[str, Session]`。GameRegistry 管理“哪个群在玩哪个游戏”的映射。 ### 持久 RPG 系统:独立 Store 适用于用户数据跨游戏 session 持久化的场景(如牛牛大作战)。不走 GameRegistry,直接在 `src/quickquip/app/message_pipeline.py` 中初始化单例,在 `src/quickquip/adapters/nonebot/commands.py` 或对应 `command_parts/` 中注册独立命令。 +以下存储骨架展示连接管理;实际实现还需创建数据目录并定义 `_ensure_schema()`: + ```python +from pathlib import Path +import sqlite3 + class MyRPGStore: def __init__(self, path: str = "data/my_rpg.db"): self.path = Path(path) @@ -124,6 +132,10 @@ GameConfig 1. 在 `config.py` 中新增 dataclass: ```python +from __future__ import annotations +from dataclasses import dataclass, field, fields +from typing import Any + @dataclass(slots=True) class MyGameConfig: min_bet: int = 20 @@ -149,7 +161,7 @@ my_game=MyGameConfig.from_dict(data.get("my_game")), 4. 游戏构造函数接收 config: ```python -def __init__(self, config: MyGameConfig | None = None, ...): +def __init__(self, config: MyGameConfig | None = None): self._config = config or MyGameConfig() ``` @@ -170,7 +182,9 @@ game_registry.register(MyGame(config=games_config.my_game)) 金币系统对游戏开发者暴露以下接口: -```python +下面的 API 签名摘要用于查找方法;实现见 `src/quickquip/games/economy.py`: + +```text class GameEconomyStore: # 账户查询 def get_balance(self, user_id: str, group_id: str) -> dict @@ -197,11 +211,7 @@ class GameEconomyStore: def sign_in(self, user_id: str, group_id: str, today: str = "") -> dict ``` -**要点**: -- 所有方法自动 `_ensure_account`,无需预先创建账户 -- `transfer_gold` 使用 `BEGIN IMMEDIATE` 保证原子性 -- `deduct_gold` 有余额检查(`WHERE gold >= ?`),不会出现负数 -- 每个游戏调用方应自行处理 `if self._economy:` 的 None 检查(支持无金币模式) +账户查询与变更会自动创建缺失账户;排行读取群内已有账户。`transfer_gold` 使用 `BEGIN IMMEDIATE` 保证原子性,`deduct_gold` 通过 `WHERE gold >= ?` 检查余额。支持无金币模式的游戏在调用前检查 `self._economy is not None`。 --- @@ -227,11 +237,11 @@ class GameEconomyStore: ## 设计原则 -1. **游戏数据按群隔离** — 金币账户、BaseGame session 的 key 都是 `group_id` -2. **纯文本输出** — 不使用 HTML/图片渲染,消息即时送达 +1. **游戏数据按群隔离** — 金币账户按用户与群共同定位;BaseGame session 按 `group_id` 定位 +2. **纯文本输出** — 游戏结果提供回复文本,由消息管线发送 3. **自带超时** — 所有交互式游戏必须有 `expires_at` 机制,防止僵尸 session 4. **金币 None-safe** — 所有 `self._economy` 调用前检查 `is not None` -5. **原子操作** — 多用户金币变动用 `transfer_gold`,不要手动 add + deduct +5. **原子操作** — 多用户金币转账用 `transfer_gold` 在同一事务中完成 6. **单文件原则(session 型)** — session 型游戏一个 `.py` 文件,业务逻辑不跨文件拆分;RPG 系统可按域拆包(如 `niuniu/`) --- @@ -249,7 +259,7 @@ if now_ts > session.expires_at: session.expires_at = now_ts + TIMEOUT_SECONDS ``` -GameRegistry 不管理超时——各游戏自行在 `process()` 中检查。这样每个游戏可以有不同的超时时长。 +各游戏在 `process()` 中检查超时并执行自己的结算,GameRegistry 根据返回的 `finished` 清理关联。超时时长由各游戏配置决定。 --- @@ -276,4 +286,4 @@ _cd.clear(uid) # 手动清除 - Session 型游戏通过 `/game start`、`/game stop`、`/game score` 统一入口 - 游戏内消息(如“拿牌”、“开枪”)由 `GameRegistry.process()` 统一分发 - RPG 系统在 `src/quickquip/adapters/nonebot/commands.py` 或对应 `command_parts/` 中用 `on_command()` 独立注册 -- 命令别名用 `aliases=` 参数(如 `aliases={"签到"}`),不用重复注册 +- 命令别名通过 `aliases=` 参数声明,如 `aliases={"签到"}` diff --git a/skills.example/self-docs/references/docs-dev-llm-configuration-api.md b/skills.example/self-docs/references/docs-dev-llm-configuration-api.md new file mode 100644 index 00000000..e7d2a609 --- /dev/null +++ b/skills.example/self-docs/references/docs-dev-llm-configuration-api.md @@ -0,0 +1,113 @@ + + +# LLM 配置后端接口 + +本文供配置 API 调用方和维护者查阅。LLM 配置使用 `data/llm_config.db`,Web Admin、CLI 与 Bot 通过同一个应用服务保存草稿、建立快照和提交控制任务。人格文件、Skill 内容、群覆盖、历史记录、用量和 Trace 由各自存储维护。 + +接口前缀为 `/ops/api/llm-configuration`,需要现有管理员会话 Cookie `quickquip_admin_session`。写请求必须携带同源 `Origin` 或 `Referer`。请求体上限为 1 MiB,分块传输也执行此限制。完整机器契约见 [OpenAPI JSON](llm-configuration.openapi.json);运行中的管理 API 在 `/openapi.json` 提供同一字段约束。 + +前端设计与操作流程见[前端交付契约](llm-configuration-frontend.md),其中说明中文字段含义、参数继承控件、保存与应用、冲突处理及设计验收场景。 + +配置交换格式为 JSON,`format_version` 当前为 `2`。顶层字段为 `providers`、`style_profiles`、`pricing`、`routes` 与 `settings`。渠道身份为公开 `id`,模型身份为 wire 名称;引用使用 `{provider_id, model}`,支持包含 `/` 的模型名称。认证信息保存 `.env` 变量名称或 `${VARIABLE}` 引用。导出、字段错误及审计日志保留引用,运行时展开值不进入接口。 + +format 2 的价格必须指定已存在的 `provider_id`。导入、读取历史快照与编译时,format 1 文档按确定规则升级:`渠道ID/模型` 形式的兜底价格归属到该渠道,已有同键价格时移除;纯模型名兜底价格只在恰好一个尚未定价的渠道配置了该模型时归属到该渠道,其余移除;引用不存在渠道的价格移除。不可变快照保持原字节,读取接口返回升级后的文档。 + +## 修订与读写 + +| 请求 | 响应与行为 | +|---|---| +| `GET /metadata` | 请求参数和设置字段的类型、范围、枚举、默认值、继承及覆盖能力 | +| `GET` 根路径,`?source=draft` | `{revision, document}`;省略 source 使用 draft,source 支持 published 或快照修订号;实际 URL 使用前缀且无末尾斜杠 | +| `PUT` 前缀 | `{expected_draft_revision, document, note?}`,完整替换草稿,返回 `{revision, document}`;note 见下文版本标记 | +| `GET /providers` | `{revision, version, providers}` | +| `GET /providers/{id}` | 聚合 `{revision, provider, pricing, effective_parameters}`;pricing 为该渠道的价格,有效参数按模型名索引 | +| `PUT /providers/{id}` | `{expected_draft_revision, value: provider}`,新增或完整替换渠道 | +| `PATCH /providers/{id}` | `{expected_draft_revision, fields}`,省略字段保持原值,渠道 id 保持稳定 | +| `DELETE /providers/{id}?expected_draft_revision=N` | 删除无引用渠道;引用冲突附上依赖列表 | +| `PUT /providers/{id}/models` | `{expected_draft_revision, value: models}`,完整替换有序模型条目 | +| `PUT /providers/{id}/aliases` | `{expected_draft_revision, value: aliases}`,完整替换有序别名条目 | +| `GET /providers/{id}/effective?model=wire` | `{revision, parameters}`,每个字段包含 `configured/value/source` | +| `GET /sections/{section}` | `{revision, value}`,section 为 style_profiles、pricing、routes 或 settings | +| `PUT /sections/{section}` | `{expected_draft_revision, value}`,完整替换对应配置域 | +| `PUT /settings/{name}` | `{expected_draft_revision, value}`,替换单一设置域 | +| `POST /batch` | `{expected_draft_revision, changes:[{section,value}]}`,一次事务完整替换多个配置域 | + +所有草稿有效修改增加修订号。相同内容保存保持原修订号。版本冲突返回 `409 revision_conflict`;调用方重新取得草稿并处理冲突后提交。渠道或模型参数的 `null` 表示继承,`false`、合法零值、空字符串及空对象表示显式值。`extra_body` 覆盖整个对象。 + +`/llm-effort` 兼容入口读取草稿修订;`PUT /llm-effort/{id}` 需要 `{expected_draft_revision, effort}`,保存后返回 `effect: saved_draft`。旧 `/config/llm` 入口返回 `410 llm_configuration_migrated`。 + +## 校验、导入与历史 + +| 请求 | 响应与行为 | +|---|---| +| `POST /validate` | `{document?}`;省略 document 校验草稿,合法时返回 `{valid:true,warnings,dependencies}`,非法文档通过错误响应报告 | +| `GET /dependencies?source=draft` | `{dependencies:[{field_path,target}]}` | +| `GET /diff?source=draft&target=published` | 校验报告加 `{changes:[{field_path,before,after}],components}`;before 为 target,after 为 source;components 为 `{mcp_reconnect:boolean,reschedule:string[]}` | +| `POST /import/preview` | `{expected_draft_revision,document}`,返回规范化 document、差异、警告及依赖,不写入 | +| `POST /import` | 相同请求格式,完整替换草稿;应用为独立操作 | +| `GET /export?source=draft` | 直接返回配置 JSON,支持 published 或快照修订号 | +| `GET /history` | `{snapshots:[{revision,created_at,saved_at,note}]}`;第一版保留全部执行快照 | +| `PUT /note` | `{source,note}`,source 为 draft、published 或快照修订号,返回 status | +| `POST /history/{revision}/restore` | `{expected_draft_revision}`,将历史内容保存为当前草稿,修订号保持递增 | + +历史列表包含应用和探测使用过的快照,是否成功发布需结合任务结果与发布版本判断。恢复内容与当前草稿相同时保持原修订号。差异中的数组可能整体返回,前端可按公开身份组织增删改展示。 + +计费按 `(provider_id, model)` 精确匹配价格,`model` 为配置中的模型 ID,即请求发出的模型名。上游响应回报的模型名用于观测记录。已启用渠道的模型缺少价格产生 `price_missing` 警告,价格引用渠道未配置的模型产生 `price_orphan` 警告,导入 format 1 文档时每条价格升级结果产生 `pricing_upgraded` 警告。价格为零表示免费;缺失价格为未定价;缓存价格 `null` 使用输入价格。价格修改影响后续任务,历史费用保持原值。 + +## 版本标记 + +界面以“YYYY-MM-DD HH:mm (备注)”标记修订,接口以自增修订号识别快照并检查并发修改。`config_revisions` 记录每个草稿修订的保存时刻 `saved_at` 与单行备注 `note`(最多 120 字符,换行折叠为空格),各接口以 `{saved_at, note}` 返回。快照的 `created_at` 是应用或探测建立快照的时刻。保存与应用可携带 note;未携带时,尚未发布的草稿沿用上一修订的备注,发布后的首次保存从空备注开始。`PUT /note` 可修改当前草稿或任意快照修订的备注,不改变修订号。 + +schema 3 新增 `config_revisions`。schema 2 初始化升级时按快照时间回填,未建立快照的草稿保存时间未知(`saved_at: null`);草稿文档同时就地升级到交换格式 2,修订号不变。升级同样要求停止服务后执行 `init --services-stopped`,升级报告写入 `config_migrations`(source 为 `schema-3`)。 + +## 应用、探测与运行状态 + +`POST /apply` 接受 `{expected_draft_revision,request_id,note?}`,返回 HTTP `202` 与控制任务。任务创建时绑定不可变目标快照,继续编辑草稿不会改变该任务。任意时刻最多一个未结束的应用;再次提交返回 `409 operation_conflict`。相同请求编号和目标重复提交返回原任务。 + +```json +{ + "id": "example-operation-id", + "kind": "apply", + "target_revision": 8, + "status": "queued", + "stage": "queued", + "payload": {}, + "result": null, + "error": null, + "created_at": 1791680000.0, + "updated_at": 1791680000.0 +} +``` + +`GET /operations/{id}` 查询任务,`GET /operations?limit=20` 返回最近任务,`POST /operations/{id}/cancel` 取消排队任务。状态为 `queued/running/succeeded/failed/cancelled`,只有排队任务可取消。领取令牌供 bot 内部续租与结果写入,Web 响应排除该令牌。`202` 表示已经入队,最终结论通过任务状态取得。 + +`POST /probe` 接受 `{source,provider_id,model,request_id}`,使用 bot 环境执行单次模型请求,不发布草稿。响应额外包含 `may_charge: true` 和 `automatic_retry: false`。运行中探测被中断后返回 `probe_result_unconfirmed`,不会自动重发。`POST /diagnostics/providers/probe` 使用相同任务契约;`POST /diagnostics/sample-request` 支持 `system_prompt`、`user_prompt`、`stream` 和 `max_output_tokens` 等采样请求字段。 + +`GET /status` 返回数据库 schema、草稿及发布版本与各自的 `draft_version`/`published_version`,以及 bot 的 `boot_id`、`loaded_revision`、`loaded_version`、应用版本、心跳时间、观察时间及组件状态。bot 心跳超过 20 秒时 `stale: true`;尚未回报时 `bot: null`。MCP 的连接及工具发现状态属于组件状态,配置版本接纳和远端组件就绪分别回报。 + +活动任务从 `/operations` 查询,`/status` 提供配置与 Bot 状态。返回的时间为 Unix 秒。有效参数的 `configured` 通常为 `{provider,model}`,`context_window` 的 configured 为整数或 null;有效值仅解析配置层级,不含具体调用或会话覆盖。 + +`GET /environment?source=draft` 返回配置引用的变量名、bot 观察到的已配置布尔值及 `available/stale` 状态。bot 尚未观察到变量时 `configured: null`。接口不返回变量内容。 + +## 错误契约 + +新配置接口统一返回 `{code,message,field_path,details}`。字段错误为 `422`,修订、依赖或控制任务冲突为 `409`,找不到快照或任务为 `404`,请求体超限为 `413`。认证与同源保护保持 `401/403`。字段错误排除客户端认证输入值。 + +```json +{ + "code": "revision_conflict", + "message": "Draft changed since it was read", + "field_path": "", + "details": {"expected": 7, "actual": 8} +} +``` + +稳定错误代码包括 `invalid_field`、`dependency_conflict`、`literal_secret`、`revision_conflict`、`operation_conflict`、`database_busy`、`unsupported_schema`、`database_corrupt`、`stale_claim` 和 `probe_result_unconfirmed`。前端按 code 处理状态,message 用于展示。 + +更新约束或路由后运行 `python scripts/ci/export_llm_configuration_openapi.py`,同时提交生成的 OpenAPI 文件。接口回归使用隔离 SQLite 数据库与管理员会话,覆盖跨进程初始化、事务、租约、并发编辑、HTTP 鉴权、同源保护、导入预览与敏感数据边界。 + +## 请求指纹配置 + +Provider 的固定字段 `client_fingerprint` 缺省为 `default`,由渠道选择;模型参数覆盖该字段会被拒绝。default 的 Chat、Responses、Anthropic、Gemini 分别引用 OpenCode 2、Codex、Claude Code、Gemini CLI 的协议策略;引用与显式选择适用同一动态字段覆盖保护。metadata 的 `client_fingerprints` 数组提供 `id`、`label` 和支持的 `protocols`;Web 按协议提供选择。`settings.runtime.client_cluster` 是可选全局字符串,沿用草稿、保存和应用流程。四个 `x-quickquip-*` 专有头由自有 Profile 管理,附加头冲突返回 `profile_owned_header`。Profile 文件与安装身份契约见[请求指纹 Profile](../admin/client-fingerprints.md)。配置 schema 1、2 在初始化中事务性升级为 3,见本页[版本标记](#版本标记)。 + +`settings.runtime.claude_code_device_id` 是可选全局 64hex 字符串,写入时规范为小写;空值表示持久随机设备身份。它保存在权威配置 JSON、config_settings 文档及不可变发布快照中,不增加关系索引列。Claude Code 自动 Body 策略拒绝覆盖 `system`、`messages`、`tools` 和受管 `metadata.user_id`,返回 `profile_owned_body`;非法 `metadata` 类型返回 `invalid_field`。 diff --git a/skills.example/self-docs/references/docs-dev-llm-configuration-frontend.md b/skills.example/self-docs/references/docs-dev-llm-configuration-frontend.md new file mode 100644 index 00000000..3d710321 --- /dev/null +++ b/skills.example/self-docs/references/docs-dev-llm-configuration-frontend.md @@ -0,0 +1,358 @@ + + +# LLM 配置管理前端交付契约 + +本页供负责 QuickQuip 管理前端设计与实现的 Agent 使用。目标是让管理员能理解配置、完成修改,并确认机器人实际使用了哪个版本。页面结构、视觉风格和控件布局由前端设计 Agent 决定;本文规定配置含义、操作结果和接口契约。 + +开发入口为 `/ops/#/llm-config`。后端接口、数据库和控制任务已经实现,前端可以重新组织现有页面。完整字段约束见 [OpenAPI](llm-configuration.openapi.json)、[API 文档](llm-configuration-api.md)和[公开 JSON 示例](../../config/llm.json.example)。管理员操作背景见[配置管理说明](../admin/llm-configuration.md)。 + +## 1 用户需要完成的事 + +管理员应能顺利完成以下操作: + +1. 添加一个渠道,填写地址、协议和密钥变量名,添加模型并选择渠道默认模型。 +2. 找到某个模型,调整它的请求参数,理解哪些值继承自渠道。 +3. 选择默认聊天渠道,以及报告、快速判定和图片预处理使用的模型。 +4. 保存草稿,查看与当前已发布配置的差异,再发起应用。 +5. 确认 Bot 是否在线、是否加载了目标版本、MCP 等组件是否就绪。 +6. 导入配置前检查变更,导出配置,或把历史快照恢复成草稿。 + +界面文案使用中文业务名称。原始字段名可出现在帮助文字或高级视图中。普通操作通过表单、选择器、列表和顺序调整完成;JSON 用于导入导出和适合自由对象的高级字段。 + +### 信息组织建议 + +以下分区可作为设计起点,具体导航与布局由前端决定。 + +| 分区 | 用户看到的内容 | +|---|---| +| 概览与变更 | 未保存编辑、草稿版本、已发布版本、Bot 加载版本、待应用差异、当前应用任务 | +| 渠道与模型 | 可搜索的渠道列表、渠道连接信息、模型列表、别名、默认参数、模型覆盖和价格 | +| 模型选择 | 默认聊天渠道、四类报告的候选顺序、快速判定、图片预处理 | +| 行为与能力 | 触发方式、工具、Skill 设置、MCP 服务、记忆与回复行为、报告设置 | +| 高级参数 | 重试、上下文纪元、输入与媒体预算、协议特有参数 | +| 配置交换与历史 | JSON 导入预览、导出、历史快照、任务记录 | + +渠道较多时,先选择渠道,再加载其详情;模型较多时,按需展开模型。日常参数和高级参数应具有清楚的层次。列表需要搜索、空状态和停用标记。 + +## 2 保存与应用 + +### 四种状态 + +| 名称 | 含义 | 用户可见文案示例 | +|---|---|---| +| 页面中的编辑 | 当前浏览器表单里的修改,还未提交 | “有未保存的修改” | +| 草稿 `draft_revision` | 已保存到数据库,等待应用 | “草稿 2026-10-14 15:30 (接入新渠道)” | +| 已发布 `published_revision` | Bot 已接纳的发布指针,重启从这里加载 | “已发布 2026-10-12 09:05” | +| Bot 加载 `loaded_revision` | Bot 最近心跳回报的实际内存版本 | “Bot 已加载 2026-10-12 09:05” | + +修订号供接口检查并发修改和识别快照;界面使用保存时间与备注标记版本。版本标记由 `draft_version`、`published_version`、`loaded_version` 及历史条目的 `{saved_at, note}` 组合为「YYYY-MM-DD HH:mm (备注)」;保存时间未知时显示“未记录时间”。备注随保存或应用提交,也可通过 `PUT /note` 修改任意快照修订。 + +“保存草稿”和“应用配置”是独立操作。保存成功后显示“草稿已保存”;收到应用任务的 HTTP `202` 后显示“已提交应用,等待 Bot 执行”。应用任务成功、Bot 心跳新鲜且加载版本符合目标时,才展示实际加载成功。发布指针和内存切换之间可以短暂出现版本差异。 + +新任务使用应用后的版本。已经开始的对话、报告和计费继续使用任务开始时的配置与价格。重启 Bot 加载已发布快照,草稿继续等待应用。 + +### 保存流程 + +1. 读取草稿,保留返回的 `revision` 作为编辑基准,同时保留一份基准内容。 +2. 用户修改本地表单,显示未保存状态。 +3. 保存请求带上 `expected_draft_revision`。 +4. 成功后采用后端返回的规范化 `document` 和新 `revision`,更新编辑基准。 +5. 内容没有变化时,后端可以返回原修订号。 + +修订号覆盖整个配置库。即使两个管理员编辑不同渠道,也可能发生版本冲突。遇到 `409 revision_conflict`,保留本地编辑,提供重新读取、比较和处理冲突的入口;用户确认处理方式后再保存。避免自动重试覆盖他人的修改。切换页面、关闭编辑器或恢复历史时,应处理未保存内容。 + +### 应用流程 + +1. 用户先保存要应用的内容,再检查草稿与已发布配置的差异。 +2. 提交明确的草稿修订号和唯一 `request_id`。 +3. 记录返回的操作 ID,轮询任务与运行状态。 +4. 应用期间可以继续编辑和保存。任务始终使用提交时固定的快照。 +5. 同时最多一个未结束的应用任务;展示已有任务进度,暂缓提交新的应用。 + +例如应用目标是 15:30 保存的修订,用户随后在 15:40 再次保存。任务成功后,页面显示 Bot 已加载 15:30 版本,并提示 15:40 草稿等待应用。 + +## 3 配置对象与表单语义 + +JSON 顶层固定为 `format_version`、`providers`、`style_profiles`、`pricing`、`routes`、`settings`。`format_version` 当前为整数 `2`;导入 `1` 时后端按确定规则升级价格并在预览中逐条说明。 + +### 渠道 + +渠道是一套连接信息及其模型列表。公开 ID 保持稳定,API 的 PATCH 不允许修改 ID。 + +| 字段 | 人类可读名称与规则 | +|---|---| +| `id` | 渠道 ID;唯一,仅含英文字母、数字、点、下划线、连字符 | +| `protocol` | 协议;`openai`、`claude`、`gemini`、`openai_responses` | +| `base_url` | 服务地址;必填 | +| `api_key_env` | 密钥环境变量名,如 `OPENAI_API_KEY`;必填,填写变量名称 | +| `default_model` | 渠道默认模型;必须是该渠道模型列表中的真实模型名 | +| `enabled` | 渠道是否启用 | +| `client_fingerprint` | 客户端 Profile;从 metadata 返回的当前协议可用模板中选择,缺省为 `default` | +| `auth_method` | 认证方式;`api_key` 或 `bearer` | +| `headers` | 额外请求 Header;键值编辑器,认证值使用环境变量引用 | +| `fallback_urls` | 有序备用地址列表 | +| `proxy` | 代理地址;空字符串表示没有显式代理 | +| `parameters` | 渠道默认请求参数 | +| `models`、`aliases` | 有序模型列表、有序别名列表 | + +创建渠道的编辑流程需要同时准备至少一个模型和默认模型,提交时形成合法渠道。停用保留配置、会话和历史记录。删除可能受到默认选择、报告候选、别名等引用限制,需要向用户展示依赖并引导处理。 + +### 模型与别名 + +| 字段 | 含义 | +|---|---| +| `model` | 发给上游的真实模型名,也称 wire 名;同一渠道内唯一,允许包含 `/` | +| `enabled` | 模型是否启用 | +| `vision` | 是否具备图片理解能力 | +| `context_window` | 上下文容量,单位 token;`null` 为自动识别,显式值为至少 1024 的整数 | +| `parameters` | 该模型的请求参数覆盖 | + +模型身份由 `{provider_id, model}` 共同确定。前端保持这两个字段独立,构造 URL 时分别编码。别名为 `{alias, model}`,目标必须是同渠道内的真实模型名;别名不得重复或与真实模型名冲突。报告候选等结构化引用使用真实模型名。 + +渠道、模型、别名和候选数组保持显式顺序。改变模型身份时需要同步处理引用;当前接口没有独立重命名操作。删除模型通过完整替换模型列表完成,后端会校验剩余引用。 + +### 参数继承 + +每个可覆盖字段都需要表达“继承”和“显式设置”两种意图。 + +| 数据 | 意图 | 控件行为 | +|---|---|---| +| 覆盖字段 `null` | 从更低优先级配置取得值 | 展示“继承”,并说明当前有效值与来源 | +| PATCH 省略字段 | 保留已有设置 | 保存局部修改时保留未编辑字段 | +| `false` | 显式关闭 | 保留为 false | +| 合法的 `0` | 显式零值 | 保留为 0;例如媒体预算 0 表示不限 | +| `""` | 显式空字符串 | 保留清空意图;例如思考档位为空表示使用模型默认档 | +| `{}` | 显式空对象 | 可用于清空额外请求体 | + +布尔覆盖适合“继承/开启/关闭”三态。数值字段需要独立的“继承”入口,避免把空输入直接转换为 0。`context_window: null` 的文案使用“自动识别”;设置字段的 `nullable` 和参数字段的 `inherit` 含义分别按 metadata 处理。 + +请求参数有效值优先级为:调用专用或会话覆盖 → 模型覆盖 → 渠道默认 → 具有该字段职责的全局设置 → 内置默认。部分报告会传入调用专用参数。有效值接口展示模型、渠道、全局与内置默认的解析结果。排查某次请求时,在此基础上核对会话覆盖和调用专用参数。 + +`extra_body` 每次整体替换。模型选择共享风格的 `style_profile`,与追加文本 `style_overrides` 分别覆盖渠道对应字段;最终风格由共享风格文本与有效追加文本拼接。`style_profile: ""` 表示不选择共享风格,`style_overrides: ""` 表示清空追加文本。 + +常用参数包括超时秒数、温度、最大输出 token、流式、思考档位、缓存和风格。高级参数包括 User-Agent、`extra_body`、重试策略、上下文纪元、输入预算、媒体字节预算、历史重放预算、Responses profile 和原生搜索。原生搜索 `builtin_search` 的请求效果适用于 Gemini;协议特有字段应说明适用协议。 + +### 有效值与字段约束 + +`GET /metadata` 提供 `parameters`、`settings`、`protocols` 和 `route_names`。参数 metadata 包含 `type`、`nullable`、`inherit`、`default`、可用的 `minimum/maximum/enum`,以及 `provider_override/model_override`。它提供约束;中文名称、用途说明、单位和分组由前端维护。MCP server 等嵌套结构结合 OpenAPI 和对应配置说明实现专用编辑器。 + +metadata 的 default 描述字段缺省值;模型当前继承的值从有效值接口取得。渲染表单时保持 null 的继承意图,显示缺省值不会自动形成显式覆盖。minimum 或 maximum 为 null 时,该侧没有对应范围限制。 + +例如模型温度继承渠道 0.8 时,有效值接口返回: + +```json +{ + "configured": {"provider": 0.8, "model": null}, + "value": 0.8, + "source": "provider" +} +``` + +参数来源为 `model/provider/runtime/builtin`,分别显示“模型设置/渠道默认/全局运行设置/内置默认”。`context_window` 的 `configured` 直接是整数或 null,其来源为 `model/automatic/unknown`,显示“手动指定/自动识别/未知”。风格选择和追加文本返回各自有效字段,界面可据此组织风格预览。 + +后端拒绝越界值,不会在保存时静默截断。主要约束示例:温度 0~2,超时至少 0.01 秒,输出 token 至少 1,工具最多 16 轮、每轮最多 32 次调用,搜索每轮最多 32 次。完整约束读取 metadata;后端同时校验跨字段关系,例如纪元预算顺序与回复拆分阈值。 + +## 4 模型选择 风格 价格与能力设置 + +### 模型选择 + +| 功能 | 存储位置 | 编辑语义 | +|---|---|---| +| 默认聊天 | `settings.runtime.default_provider` + 渠道 `default_model` | 渠道选择器与渠道默认模型共同决定默认聊天模型;启用聊天时需要可用选择 | +| 每日总结/每日播报/周报/月报 | `routes.daily_summary/daily_briefing/weekly_report/monthly_report` | 有序候选列表;按顺序尝试,可以调整顺序 | +| 快速判定 | `settings.quick_judge.provider_id/model` | 两项均为空时使用默认聊天选择;选定渠道且 model 为空时使用该渠道默认模型 | +| 图片预处理 | `settings.image_preprocessing` | 开启后需要选择可用视觉渠道与模型;model 为空时使用所选渠道默认模型 | + +候选项为 `{provider_id, model}` 或字符串 `"@default"`。`@default` 展示为“跟随默认聊天模型”,在运行时按任务配置解析,存储时保留该标记。空的报告候选列表使用默认聊天选择。候选显示渠道和模型,不能靠拆分 `/` 识别模型。 + +停用选项可以保留在编辑视图并标明状态。新的选择应引导到可用对象。保存检查结构和引用;应用时还检查启用状态、本地人格文件、环境变量及已有群覆盖等条件。 + +### 风格 + +`style_profiles` 为“名称 → 提示文本”的对象。提供命名列表、文本编辑和引用情况。渠道和模型从已有名称中选择共享风格,并分别编辑追加文本。人格提示词与共享风格具有独立职责,页面文案应清楚说明。 + +### 价格 + +计费单位是 `(provider_id, model)`:`provider_id` 必须是已存在的渠道,`model` 是该渠道配置的模型 ID,与请求发出的模型名一致。价格按渠道与模型精确匹配。按渠道分组展示,模型从渠道模型清单中选择;渠道已不再配置的价格行保留并提示(`price_orphan`)。删除渠道时一并删除其价格。 + +输入、输出、缓存读取、缓存写入单价均为美元/百万 token。0 表示免费;缺少价格规则表示未定价;缓存单价 `null` 表示按输入单价计费。新增规则的输入和输出单价留空,必须填写后才能保存,避免缺省 0 被当作免费。缺少价格产生警告,允许继续保存。价格变更影响后续任务,历史费用保持原值。 + +`source` 和 `confidence` 保存价格来源与确认程度,现有字段为字符串。保留已有值;日期、币种和价格历史接口当前未提供。 + +### 设置范围 + +| 设置域 | 用户应理解的内容 | +|---|---| +| `runtime` | 聊天开关、默认人格、记忆、历史、工具轮数、回复分段、高级预算与全局客户端身份字段 | +| `triggers` | 命令前缀、是否响应前缀或艾特、空问题提示 | +| `auto_search` | 自动联网搜索与每轮搜索上限 | +| `quick_judge` | 快速判定模型、超时与输出预算 | +| `image_preprocessing` | 图片预处理开关、视觉模型、提示词与输出预算 | +| `tools` | 工具清单、append/replace 模式、工具发现策略和常驻工具 | +| `skills` | Skill 开关、目录和执行/读取资源上限;Skill 文件内容由现有 Skill 页面管理 | +| `mcp` | 总开关、独立服务列表、传输方式、连接信息、工具过滤与协商设置 | +| 四类报告设置 | 各自开关、生成/发送时间、消息门槛与内容预算;模型顺序在 routes 中编辑 | + +工具名单为空时使用默认工具与已注册 MCP 工具。非空名单中,`enabled_mode: append` 在默认集合上追加,`replace` 按指定名称精确过滤。 + +MCP 按 transport 展示连接字段:stdio 使用命令,HTTP/SSE 使用地址和 Header,Docker 使用镜像与运行参数。auto/modern 协商需要 HTTP 和非空支持版本列表。报告 cron 可提供时间说明,提交使用后端支持的五段表达式。全局客户端身份字段按 metadata 展示,并链接[Profile 说明](../admin/client-fingerprints.md)解释用途、自动值与覆盖规则。 + +人格文件、Skill 内容、群级覆盖、聊天记录、用量和 Trace 使用各自存储,页面链接相应管理入口。恢复配置快照会更新配置库草稿;部署和业务数据的备份恢复见[管理员操作](../admin/llm-configuration.md)。 + +## 5 运行状态与凭据 + +Web Admin 是管理员的唯一访问入口。前端只请求 Web API;Bot 在另一进程或容器中执行应用与探测。 + +`GET /status` 返回: + +```json +{ + "schema_version": 3, + "draft_revision": 12, + "draft_version": {"saved_at": 1791690000.0, "note": "接入新渠道"}, + "published_revision": 10, + "published_version": {"saved_at": 1791680000.0, "note": ""}, + "bot": { + "boot_id": "example-boot-id", + "application_version": "1.16.2-dev.0", + "loaded_revision": 10, + "loaded_version": {"saved_at": 1791680000.0, "note": ""}, + "heartbeat_at": 1791680000.0, + "observed_at": 1791680003.0, + "stale": false, + "components": { + "core": {"state": "ready"}, + "mcp": {"state": "preparing", "servers": []}, + "environment": [{"name": "OPENAI_API_KEY", "configured": true}] + } + } +} +``` + +该样例说明响应结构,版本和时间仅用于示例。当前 schema 版本从接口取得,保持与应用版本独立。 + +- `bot: null`:Bot 尚未回报,显示“暂无 Bot 状态”。 +- `stale: true`:最近回报已过期,显示最近观察时间和“Bot 状态过期”。 +- 新鲜心跳且版本相符:可以显示 Bot 已加载目标版本。 +- `components.core.state` 为 `ready/failed`;MCP 为 `ready/preparing/degraded`,分别显示就绪、准备中、部分不可用。 + +应用成功和 MCP 远端服务就绪分别呈现。组件部分不可用时保留实际已加载版本,不把全部状态压成一个成功或失败图标。 + +Bot 约每 5 秒回报心跳,超过 20 秒标为过期。前端在轮询失败或网络断开时也需要让旧状态逐渐过期;可以用 `observed_at - heartbeat_at` 加上本地经过时间判断,避免设备时钟偏差。轮询间隔可由前端设计,建议运行状态约 5 秒、活动任务约 2 秒;页面卸载后停止轮询。 + +凭据值只来自服务器根目录 `.env`。密钥变量名和 `${VARIABLE}` 引用可以编辑;编辑器接收变量名称或引用,真实密钥在服务器上配置。认证 Header 例如 `Bearer ${OPENAI_API_KEY}`。 + +`GET /environment?source=draft` 返回 `variables:[{name,configured}]`、`available`、`stale`。`configured: true/false/null` 分别为已配置、缺失、尚无观察结果。信息来自 Bot;过期观察和新变量尚未被观察时需要标明状态。接口不会返回变量值。 + +## 6 应用任务与模型探测 + +应用请求为: + +```json +{"expected_draft_revision": 12, "request_id": "a-unique-client-request-id"} +``` + +返回任务包含 `id`、`kind`、`target_revision`、`status`、`stage`、`payload`、`result`、`error`、`created_at`、`updated_at`。时间是 Unix 秒,转换为 JavaScript 日期时乘以 1000。可能附带内部管理字段,界面只消费约定字段。 + +| status | 用户文案 | 允许的操作 | +|---|---|---| +| `queued` | 等待 Bot 执行 | 查询、取消 | +| `running` | 正在执行 | 查询进度 | +| `succeeded` | 已完成 | 展示结果及实际版本 | +| `failed` | 执行失败 | 展示原因,保留草稿,允许用户处理后重新发起 | +| `cancelled` | 已取消 | 保留记录 | + +`stage` 是阶段说明,当前包括 queued、preparing、switching、published、finished、cancelled、unconfirmed。核心状态判断使用 `status`,未知 stage 需要有通用展示。只有排队任务可以取消;取消发生竞争时重新查询任务。 + +同一次用户操作在网络重试时复用 `request_id`。更改目标或主动发起新的操作使用新编号。页面刷新后通过任务列表恢复进度,不自动提交新应用。`GET /status` 当前不提供 `active_operation`,活动任务从 `/operations` 查询。 + +探测先选择配置来源 draft、published 或指定快照修订号,再选择渠道和真实模型。它使用 Bot 环境执行固定快照的单次请求,返回 HTTP `202`;响应声明 `may_charge: true`、`automatic_retry: false`。发起前清楚显示“探测可能产生模型费用”。 + +探测不会保存页面里的未提交编辑,也不会发布草稿。用户要测试这些修改时先保存草稿并选择 draft。草稿在提交时绑定快照;如需固定已知修订号,可选择存在的历史快照。`probe_result_unconfirmed` 表示请求中断后结果未确认,禁止前端自动再次探测;用户可以明确发起新的探测。 + +## 7 导入 导出与历史 + +导入 JSON 为完整替换草稿,流程为:选择文件 → 校验与预览 → 展示增删改、警告及影响 → 用户确认保存草稿。预览不写库。确认请求继续使用预览的基准修订号;其他人修改草稿后需要重新预览。 + +导入前要处理当前未保存编辑。导入完成后独立提供应用操作。请求体最多 1 MiB,上传前按 UTF-8 字节数检查,并保留后端 `413` 处理。 + +导出明确标注来源:草稿、已发布、指定历史快照。导出内容是 JSON 配置,不含密钥值、任务、运行状态或数据库内部行号。数据库备份恢复通过 CLI/运维工具执行。 + +历史列表包含应用和探测使用过的快照;根据任务结果与发布指针标记快照的实际用途和结果。恢复历史会把快照内容保存为当前草稿,修订号保持前进;内容相同时可以保持原草稿修订号。恢复不会直接切换 Bot。 + +## 8 API 接线清单 + +下面 URL 均相对 `/ops/api/llm-configuration`。所有请求使用现有管理员会话 Cookie;写请求沿用同源保护。新页面可以复用 `frontend/src/api/index.ts`,其 BASE 已包含 `/ops`,调用时使用 `/api/llm-configuration`,避免重复前缀。结构化错误位于该封装的 `error.data`。 + +| 功能 | 请求 | 主要响应 | +|---|---|---| +| 字段约束 | `GET /metadata` | parameters、settings、protocols、route_names | +| 读取配置 | `GET ?source=draft` | `{revision,document}`;source 也支持 published 或快照号 | +| 保存完整草稿 | `PUT` 根路径,`{expected_draft_revision,document}` | `{revision,document}` | +| 渠道列表/详情 | `GET /providers`、`GET /providers/{id}` | 列表,或含 provider、pricing、effective_parameters 的聚合详情,均带 revision | +| 新增/替换渠道 | `PUT /providers/{id}`,`{expected_draft_revision,value}` | 更新后的完整配置 envelope;value.id 与 URL 一致 | +| 渠道局部修改 | `PATCH /providers/{id}`,`{expected_draft_revision,fields}` | envelope;省略字段保留,parameters 按字段修改 | +| 删除渠道 | `DELETE /providers/{id}?expected_draft_revision=N` | envelope 或依赖冲突 | +| 模型/别名 | `PUT /providers/{id}/models` 或 `/aliases`,`{expected_draft_revision,value}` | 完整替换相应有序数组,返回 envelope | +| 有效参数 | `GET /providers/{id}/effective?model=wire&source=draft` | `{revision,parameters}` | +| 配置域 | `GET/PUT /sections/{section}` | GET 为 `{revision,value}`;PUT 带 expected_draft_revision 和完整 value | +| 单个设置域 | `PUT /settings/{name}`,`{expected_draft_revision,value}` | envelope;value 完整替换该设置域,缺省字段会规范化为默认值 | +| 多域事务 | `POST /batch`,`{expected_draft_revision,changes:[{section,value}]}` | 一次事务替换所列域,返回 envelope | +| 校验 | `POST /validate`,`{document}` 或 `{}` | 合法时 `{valid:true,warnings,dependencies}`;非法时 422/409 错误 | +| 依赖 | `GET /dependencies?source=draft` | `{dependencies:[{field_path,target}]}` | +| 差异与影响 | `GET /diff?source=draft&target=published` | 校验报告 + changes、components | +| 导入预览 | `POST /import/preview`,`{expected_draft_revision,document}` | 规范化 document、基准修订号、校验报告、changes、components | +| 导入保存 | `POST /import`,相同请求格式 | envelope | +| 导出 | `GET /export?source=published` | 直接返回配置 JSON | +| 历史/恢复 | `GET /history`;`POST /history/{revision}/restore`,`{expected_draft_revision}` | 快照列表;恢复返回 envelope | +| 应用 | `POST /apply`,`{expected_draft_revision,request_id}` | HTTP 202 + 任务对象 | +| 探测 | `POST /probe`,`{source,provider_id,model,request_id}` | HTTP 202 + 任务对象及计费/重试声明 | +| 任务记录 | `GET /operations?limit=20`、`GET /operations/{id}` | `{operations:[...]}` 或单个任务 | +| 取消排队任务 | `POST /operations/{id}/cancel`,`{}` | 任务对象 | +| 实际状态/凭据状态 | `GET /status`、`GET /environment?source=draft` | 见运行状态章节 | + +sections 允许 style_profiles、pricing、routes、settings;batch 还允许 providers。batch 每项含 section 和 value,最多 100 项。它是多个顶层域的完整替换事务,提交内容需要包含这些域内所有应保留的条目。 + +差异返回 `changes:[{field_path,before,after}]`。默认查询的 before 是 published,after 是 draft。数组变化可能整段返回,前端可按公开身份整理成人类可读的增删改列表。 + +`components` 是对象 `{mcp_reconnect:boolean,reschedule:string[]}`。它提供配置层面的影响概览;报告设置变化会列入 reschedule,Bot 执行时处理实际调度替换。价格等无关变化不需要 MCP 重连。普通校验检查结构和引用。应用时检查 Bot 侧的环境变量、人格文件等条件;模型连通性通过探测任务检查。 + +## 9 错误与文案 + +新配置 API 错误为 `{code,message,field_path,details}`。按 code 和 HTTP 状态决定交互,展示中文解释并保留后端诊断信息。field_path 可能使用点路径或数组下标;映射到具体控件,无法定位时显示在所属区域。details 的结构随错误代码变化,应稳健读取。 + +| 状态或错误 | 用户需要得到的反馈 | +|---|---| +| 401 | 登录已失效,重新登录;保留可恢复的编辑状态 | +| 403 | 请求被同源保护拒绝,检查访问与代理地址 | +| 422 invalid_field/invalid_reference | 明确哪个字段或目标需要修改 | +| 422 literal_secret | 填写环境变量引用,并在服务器 `.env` 配置真实密钥 | +| 409 revision_conflict | 他人修改了草稿;保留编辑并处理冲突 | +| 409 dependency_conflict | 存在引用;展示位置或引用对象,必要时查询 dependencies | +| 409 operation_conflict | 存在应用任务,或请求编号已用于其他操作;查询任务后处理 | +| 409 database_busy | 配置库正忙;保留编辑,允许稍后重试 | +| 404 not_found | 渠道、任务或快照不存在,更新列表 | +| 413 body_too_large | 请求超过 1 MiB | +| unsupported_schema/database_corrupt | 配置库无法正常使用,提示管理员通过运维入口处理 | +| 任务 missing_environment/missing_persona/vision_required | 缺少 Bot 侧凭据、人格文件或视觉能力;展示可操作的修复提示 | +| 任务 probe_result_unconfirmed | 探测结果未确认,等待用户明确决定是否再次请求 | +| 网络错误 | 当前数据未确认;保留编辑、停止成功提示、让运行状态按时间过期 | + +`price_missing` 等 warning 不等同于字段错误。展示警告和影响,允许用户按契约继续保存。后端无效文档通常通过错误响应报告,不能只依赖 `valid:false`。 + +## 10 前端验收场景 + +1. 新库无渠道、聊天关闭:页面有明确添加入口,保存合法配置。 +2. 多渠道与长模型列表:能快速定位某一模型,界面按需展示参数。 +3. 三态布尔、继承数值、显式空字符串、合法 0、空 extra_body:保存往返保持意图。 +4. 保存新草稿后 Bot 加载已发布版本:显示“已保存,等待应用”。应用期间再次保存:任务绑定提交时的修订。 +5. 两个页面编辑同一修订:第二次保存遇到 409,原编辑内容可继续比较和处理。 +6. 删除有引用的模型或渠道:显示依赖,用户能找到对应配置位置。 +7. Bot 从未回报、心跳过期、轮询断网、MCP 部分不可用:四种状态清楚呈现。 +8. queued 可以取消,running 无取消入口;刷新页面后能找回活动任务。 +9. JSON 预览不修改草稿,导入保存不自动应用;恢复历史不自动切换 Bot。 +10. 模型名含 `/`、免费价格、未定价、缓存价继承、凭据观察未知:正确读写与展示。 +11. 探测标明来源、目标和可能计费;未知结果不自动重发。 +12. 桌面与移动布局可操作,键盘可定位控件,字段标签、单位、帮助和错误可读。 + +测试替身使用公开示例和隔离配置库,覆盖上述状态;联调只经 Web API。交付运行 `pnpm type-check`、`pnpm test`、`pnpm build`,并展示关键操作的桌面与移动效果。已有前端 API 封装与类型可复用,响应结构以本契约和后端实际响应为准,尤其留意 components 为对象、有效值 configured 的两种结构,以及 status 不带活动任务。 diff --git a/skills.example/self-docs/references/docs-dev-llm-module.md b/skills.example/self-docs/references/docs-dev-llm-module.md index 7b2f5cd5..aa2d1de4 100644 --- a/skills.example/self-docs/references/docs-dev-llm-module.md +++ b/skills.example/self-docs/references/docs-dev-llm-module.md @@ -2,29 +2,13 @@ # QuickQuip LLM 模块说明 -## 1. 模块定位 - -QuickQuip 的 LLM 模块是建立在原有规则机器人之上的**显式触发扩展层**。 - -它在保留原有规则回复体系的前提下,提供一套受开关、触发条件和上下文边界约束的 LLM 能力: - -- 可按群开关 -- 可按群切换 provider / model / persona -- 仅在指令或艾特时触发 -- 带有限定人格注入 -- 带有严格边界的短期上下文与长期记忆 - -的 LLM 能力。 +本文面向 LLM 运行时维护者,说明消息触发、Session 与上下文、请求组装、人格和工具的契约。配置与部署操作见[管理员文档](../admin/llm-configuration.md),实现排障见本页第 11 节。 -当前模块还额外覆盖两类能力: +## 1. 模块定位 -- 显式触发下的图片理解 -- 显式触发下的语音消息转写 -- 基于项目内搜索后端的联网搜索 -- gemini provider 的内置联网搜索(`google_search` grounding,按 provider 开启) -- 标准化工具调用(身份查询、记忆查询、联网搜索) +QuickQuip 的 LLM 模块为规则机器人提供对话扩展。管理员按群或私聊选择渠道、模型和人格,并控制触发、记忆与工具。默认入口为配置前缀、艾特机器人和 `/search`;群聊唤醒按第 3 节的开关与条件启用。 -如果后续需要把外部工具后端扩展为 MCP,单独查看 [mcp-integration.md](mcp-integration.md)。当前文档只描述已经落在项目内的 LLM 与工具调用实现。 +模块支持图片理解、语音转写、项目搜索后端、Gemini 原生搜索和工具调用。MCP 服务作为外部工具来源接入同一注册表,契约见[MCP 集成说明](mcp-integration.md)。 LLM 运行时在 `LLM_TRACE_FLAG_FILE` 指向的开关文件存在时,把每次 HTTP 尝试写入 `data/llm_trace.db`。请求正文取自实际交给 HTTP 客户端的 UTF-8 JSON 序列化文本;普通响应保留 JSON 解析前的服务端文本;流式响应完整消费 SSE 后,由协议客户端重建 OpenAI Chat Completion、Claude Message、Gemini GenerateContent 或 OpenAI Responses 完整响应对象,同时保留 SSE 传输原文供管理员按需核对。索引、正文和单调递增的状态事件分开存储,Web Admin 先读取轻量调用元数据,管理员选择记录后再加载完整 Header 与正文。`run_tool_call_loop` 为一轮完整交互分配 Agent Loop ID,重试、故障切换和工具结果回送产生的 HTTP 调用按组内序号排列。 @@ -36,74 +20,64 @@ LLM 运行时在 `LLM_TRACE_FLAG_FILE` 指向的开关文件存在时,把每 --- +### 1.2 请求指纹与身份投影 + +`client_fingerprints` 模块负责 TOML Profile 解析、启动目录快照、安装身份持久化和请求投影。Provider 固定字段 `client_fingerprint` 选择模板,编译时绑定全局 cluster 和数据目录;模型覆盖保留绑定。四协议在各自认证与 Body 映射边界调用共享 Header/Body 投影接口。OpenCode 2 的 Chat Body 和物理尝试追踪在实际 URL、流式模式确定后进行最终投影;其他 Profile 的早期 Body 接口保留载荷原样。Codex 的可选 body_bindings 在实际 HTTP 尝试边界投影业务 Session,只补充缓存键,保留适配器的其他 Body 字段与序列化策略。模板、字段顺序和端点兼容条件随 TOML 分发。Header 布局作用于 HTTPX 请求对象;HTTP/1.1 的 h11 序列化将 Host 移至首位,真实 socket 验证覆盖该边界和其余字段的排布。 + +`LLMRequest.session` 是业务 Session,`epoch_id` 是不可变的请求纪元快照。EpochManager 拥有聊天窗口 UUID,TurnRequestAssembler 在历史加载及预算重组后取值;独立任务入口创建临时纪元,工具和识图按调用链传递。底层自有 Profile 请求必须提供 UUIDv4。动态投影头不参与 Responses 内容兼容指纹。管理员配置与文件格式见[请求指纹 Profile](../admin/client-fingerprints.md)。 + +OpenCode 的 Session ID 是业务 Session 的纯派生,恢复后稳定,纪元推进不旋转。Codex 的 session-id、thread-id、x-client-request-id 和 prompt_cache_key 共用业务 UUID 投影;绑定字段由 Profile 拥有,Provider/Model 手工覆盖在配置及运行时拒绝。请求级 ContextVar 保存冻结请求和逻辑 parent span;HTTP 尝试生成新 span 并在 trace 记录前完成 Header/Body 投影,重试和备用地址各自生成。取消、异常和嵌套调用退出时恢复上下文。HTTP 适配器保留鉴权、代理、超时、SSE 解析和计量职责;Profile 不访问业务存储或替换人格与工具策略。 + ## 2. 当前代码结构 -LLM 相关核心文件如下: - -- `src/quickquip/adapters/nonebot/commands.py` - - 负责 `/llm`、`/search` 等命令注册;注册逻辑按域拆到 `command_parts/`(llm / memory / sts / media 等) -- `src/quickquip/adapters/nonebot/group_messages.py` - - 负责 NoneBot 群消息入口,并把消息交给应用层管线 -- `src/quickquip/adapters/nonebot/daily_summary_plugin.py` - - 负责每日总结/周期报告的定时任务注册与 `/summary` 命令;生成与发布编排本体在 `src/quickquip/chat/summary_jobs.py`(窗口、min_messages 门槛、persona 兜底、发布状态机) -- `src/quickquip/llm/service.py` - - 框架无关的 LLM 服务核心(`LLMService`),NoneBot2 插件从此处 re-export;群级配置解析、人格注入、身份注入、词表注入、记忆检索、工具调用循环与请求拼装均在这里完成;v1.12.1 后按域拆为 `service_parts/` 子包的 mixin 组合(scope、MCP 生命周期、内置工具、draw_svg、定时消息工具、Skill 工具、STS 单发入口、图像预处理、健康检查、状态、自动记忆、Agent Loop 运行时等,见 `service_parts/__init__.py`)。回复主链的输入收敛为 `llm/reply_types.py` 的 `ChatTurnRequest`,请求装配(替代旧闭包)、输入规范化、输出后处理与返回形状构造在 `llm/reply_chain.py` -- `src/quickquip/llm/reply_chain.py` - - 回复主链的装配与产出 shaping:`TurnRequestAssembler`(首轮与预算降级重建共用的显式装配对象)、`normalize_turn_input`、`finalize_reply_text`、`reply_result` 工厂与触发行 `raw_content` 拼装;只收显式参数,不 import `LLMService` -- `src/quickquip/llm/quick_judge.py` - - quick_judge 诊断通道(`QuickJudgeResult`、provider 选择策略、detailed 通道),`LLMService` 仅保留薄委托 -- `src/quickquip/llm/single_shot.py` - - 一次性生成入口的共享管线骨架(defectify / turmfluch / card_le_nearest),各入口差异点通过 `CommandSingleShotSpec` 显式传入 -- `src/quickquip/llm/prompting.py` - - 负责 system prompt 组装(仅跨轮稳定段,字节稳定契约)、**当轮上下文信封渲染**(`build_turn_envelope`:时间/节日/participants/memories/词表命中,组装时渲染、不落库)、场景块构建、统一发言者格式渲染与 messages 数组拼装 -- `src/quickquip/llm/summarize.py` - - 每日总结与周/月报生成逻辑(模型级联、prompt 构建);聊天记录输入统一经 `src/quickquip/chat/period_serializer.py` 压缩序列化(日分节【MM-DD 周X】→ 分钟块 `[HH:MM]` 块首带时间戳 → 块内同身份连发以 `/` 合并、复读折叠 ×N、URL 只留域名、bot 发言标记 `(bot)`)。周报与日报全量进序列化器;月报由 `src/quickquip/chat/period_serializer.py` 的 `build_monthly_chat_input` 按周公平分配 `input_char_budget` 字符预算组装(平静日整日保留,高活跃日优先用满剩余预算,放不下则等距抽稀),输出附 `【第N周 …】` 周节标题 -- `src/quickquip/llm/briefing.py` - - 每日播报生成(群人格、模型级联、失败回退;遇到非正常 finish_reason 会继续尝试下一条级联) -- `src/quickquip/app/message_pipeline.py` - - 应用组合根:chat / games / tieba 单例装配、`resolve_reply()` 规则链、`reload_chat_rules_pipeline()`、`save_all()` / `close_persistent_stores()` 与 `_ensure_llm_bindings()` -- `src/quickquip/llm/config.py` - - 负责读取 `config/llm.toml` -- `src/quickquip/llm/provider/`(包) - - 负责 OpenAI / Claude / Gemini / OpenAI Responses 四类协议适配,并处理工具调用协议映射;`complete()` 内建上游 429/5xx/网络错误的指数退避自动重试(`retry.py` 提供策略与延迟计算,所有 LLM 调用路径统一继承,探活/诊断经 `RetryPolicy.disabled()` 豁免);Gemini 原生工具回合会保留并原样回放含 `thoughtSignature` 的有序 parts;Claude 签名 thinking 块在工具循环内经通用重建回传,带工具的已关闭 Loop 跨轮走原生回放(owner 五元组匹配且签名有效时);Responses 后端为 `openai_responses/` 包(`profiles` / `request` / `response` / `stream` / `client` / `replay_guard`,`store:false` 全量回放 + 当前工具循环原生 items 回传 + call_id 记账 fail-closed,1.16 起);Responses 的历史原生回放(含 reasoning 密文)经 owner 五元组校验后跨轮重放,上游 400 时剥历史 reasoning 降级重试一次(当前循环 items 不受降级影响);v1.8.9 从单文件 `provider.py` 拆为子包(`base.py` 基类 + `openai.py` / `claude.py` / `gemini.py` 协议实现 + `factory.py` + `retry.py` + `trace.py`) -- `src/quickquip/llm/tool_loop.py` - - 负责工具调用循环编排(Agent Loop trace、会话消息推进) -- `src/quickquip/llm/thinking.py` - - 思考档位归一化层:内部六档(low/medium/high/xhigh/max/ultra)到各协议 wire 参数的映射与钳制(openai chat 家族钳制表、claude 代际分派 adaptive effort / 旧式 budget 固定值表、gemini 代际分派 thinkingLevel/thinkingBudget;openai_responses 走自身 profile 词表层);纯映射层,除 claude budget 1024 数学下限外一律照发(后端不支持由上游 400 暴露),钳制与照发决策均经节流日志 fail-visible -- `src/quickquip/llm/tool_discovery.py` - - 负责单次循环内的动态工具加载状态(`loaded_names`)与 `tool_search` / `tool_list` 元工具 handler -- `src/quickquip/llm/tool_result_pipeline.py` - - 负责工具执行前后的强制处理:参数与结果的敏感词扫描、单请求工具图片预算、非视觉模型图片降级 -- `src/quickquip/llm/tool_registry.py` - - 负责工具白名单注册、参数校验和执行调度 -- `src/quickquip/llm/skills/` - - Skill 系统域包(1.16 起):`parser`(SKILL.md frontmatter 与体积校验)、`catalog`(目录扫描、路径加固与内容校验)、`context`(catalog 块与激活标记的文本渲染)、`state`(per-会话激活状态登记)、`preset_drift`(预置副本字节级指纹与同名分叉检测,stdlib 自包含,同步脚本与运行时共用),以及 `tools/` 下的四枚工具(`activate_skill` 激活、`read_skill_resource` 读资料、`search_skill_resources` 检索、`run_skill_script` 执行脚本);`service_parts/skills.py` 负责每轮目录扫描(零延迟热部署)、Skill 描述清单注入系统提示与激活接缝;命令入口 `/skill list`。部署、安全模型与编写教程见 [../admin/skills.md](../admin/skills.md) 与 [skill-tutorial.md](skill-tutorial.md) -- `src/quickquip/llm/store.py` - - 负责 SQLite 持久化(会话/记忆/归档/群设置);v1.8.9 后按域拆为 `store_parts/` 子包的 mixin 组合 -- `src/quickquip/llm/vocab.py` - - 负责从 `llm_about/vocab.yaml` 读取群别名与黑话词表,并按需注入 -- `src/quickquip/llm/identity.py` - - 身份域:从 `llm_about/identities.yaml` 读取 QQ 号到标准身份的映射(共享身份模型 re-export),并承载当轮信封的身份编排(参与者归并 `collect_known_participants`、被艾特成员档案采集 `collect_mention_profiles`,供 turn envelope 注入) -- `src/quickquip/llm/rendering.py` - - 负责把消息段标准化为给 LLM 使用的纯文本,并解析艾特 -- `src/quickquip/llm/message_segments.py` - - 负责消息段叶子节点渲染、bot 身份集合归一化等共享小逻辑 -- `src/quickquip/llm/health.py` - - LLM 健康检查模块(llm_config、provider、database、knowledge_files、persona、tools、mcp、search、sensitive_filter、generation、image_preprocessing、runtime_bindings、auto_memory 共 13 项检查) -- `src/quickquip/llm/image_preprocessor.py` - - 图像预处理抽象接口(`ImagePreprocessor`),预留 OCR / 多模态模型转述的钩子点 -- `src/quickquip/adapters/nonebot/voice.py` - - 负责 OneBot V11 `record` 语音段提取、转码与 ASR 转写注入 -- `src/quickquip/generation/asr.py` - - 负责 ASR provider 调用,当前支持 OpenAI-compatible `/audio/transcriptions` -- `src/quickquip/common/recent_message_buffer.py` - - 负责“触发前最近群消息”内存缓冲 -- `src/quickquip/llm/inputs.py` - - 负责从消息段中提取文本触发、艾特触发和图片 URL -- `src/quickquip/search/web_search.py` - - 负责项目内 SearXNG 搜索客户端,供 `/search` 与 `search_web` 工具使用 -- `src/quickquip/llm/provider/gemini.py` + `src/quickquip/llm/rendering.py` - - 负责内置搜索的请求声明(`google_search` 工具条目)、`groundingMetadata` 解析(`LLMWebSearchReport`)与回复来源块渲染 +LLM 维护入口按职责列在下表: + +| 文件或目录 | 职责 | +|---|---| +| `src/quickquip/adapters/nonebot/commands.py` | 负责 `/llm`、`/search` 等命令注册;注册逻辑按域拆到 `command_parts/`(llm / memory / sts / media 等) | +| `src/quickquip/adapters/nonebot/group_messages.py` | 负责 NoneBot 群消息入口,并把消息交给应用层管线 | +| `src/quickquip/adapters/nonebot/daily_summary_plugin.py` | 负责每日总结/周期报告的定时任务注册与 `/summary` 命令;生成与发布编排本体在 `src/quickquip/chat/summary_jobs.py`(窗口、min_messages 门槛、persona 兜底、发布状态机) | +| `src/quickquip/llm/service.py` | `LLMService` 门面,装配会话设置、提示、记忆、工具与请求;各域实现见 `service_parts/`。回复入口采用 `ChatTurnRequest` | +| `src/quickquip/llm/reply_chain.py` | 装配首轮与预算降级请求,规范输入、处理输出并构造回复结果 | +| `src/quickquip/llm/quick_judge.py` | 结构化快速判定与诊断结果,服务门面向它委托调用 | +| `src/quickquip/llm/single_shot.py` | 独立生成的共享管线,以 `CommandSingleShotSpec` 表达各命令差异 | +| `src/quickquip/llm/prompting.py` | 组装稳定的 system prompt、当轮上下文信封、场景消息和发言者标签 | +| `src/quickquip/llm/summarize.py` | 生成每日总结与周月报,串联模型候选和压缩输入 | +| `src/quickquip/llm/briefing.py` | 每日播报生成(群人格、模型级联、失败回退;遇到非正常 finish_reason 会继续尝试下一条级联) | +| `src/quickquip/app/message_pipeline.py` | 应用组合根:chat / games / tieba 单例装配、`resolve_reply()` 规则链、`reload_chat_rules_pipeline()`、`save_all()` / `close_persistent_stores()` 与 `_ensure_llm_bindings()` | +| `src/quickquip/llm/config.py` | 保留业务配置对象;数据库应用服务读取已发布快照,`config_document.py` 负责校验、编译与模型参数解析 | +| `src/quickquip/llm/provider/` | 适配四类协议,序列化请求、解析响应、执行重试并记录 HTTP Trace | +| `src/quickquip/llm/tool_loop.py` | 负责工具调用循环编排(Agent Loop trace、会话消息推进) | +| `src/quickquip/llm/thinking.py` | 映射内部六档思考设置到各协议的参数与钳制规则 | +| `src/quickquip/llm/tool_discovery.py` | 负责单次循环内的动态工具加载状态(`loaded_names`)与 `tool_search` / `tool_list` 元工具 handler | +| `src/quickquip/llm/tool_result_pipeline.py` | 负责工具执行前后的强制处理:参数与结果的敏感词扫描、单请求工具图片预算、非视觉模型图片降级 | +| `src/quickquip/llm/tool_registry.py` | 负责工具白名单注册、参数校验和执行调度 | +| `src/quickquip/llm/skills/` | 解析、扫描与执行 Skill 包;模块职责见第 6.3 节 | +| `src/quickquip/llm/store.py` | SQLite 持久化门面,按域组合 `store_parts/` 的会话、记忆、归档与群设置实现 | +| `src/quickquip/llm/vocab.py` | 负责从 `llm_about/vocab.yaml` 读取群别名与黑话词表,并按需注入 | +| `src/quickquip/llm/identity.py` | 复用共享身份模型,归并参与者并收集被艾特成员档案 | +| `src/quickquip/llm/rendering.py` | 负责把消息段标准化为给 LLM 使用的纯文本,并解析艾特 | +| `src/quickquip/llm/message_segments.py` | 负责消息段叶子节点渲染、bot 身份集合归一化等共享小逻辑 | +| `src/quickquip/llm/health.py` | 配置、渠道、数据库、资料、工具与运行时健康检查 | +| `src/quickquip/llm/image_preprocessor.py` | 图像预处理抽象接口(`ImagePreprocessor`),预留 OCR / 多模态模型转述的钩子点 | +| `src/quickquip/adapters/nonebot/voice.py` | 负责 OneBot V11 `record` 语音段提取、转码与 ASR 转写注入 | +| `src/quickquip/generation/asr.py` | 负责 ASR provider 调用,当前支持 OpenAI-compatible `/audio/transcriptions` | +| `src/quickquip/common/recent_message_buffer.py` | 负责“触发前最近群消息”内存缓冲 | +| `src/quickquip/llm/inputs.py` | 负责从消息段中提取文本触发、艾特触发和图片 URL | +| `src/quickquip/search/web_search.py` | 负责项目内 SearXNG 搜索客户端,供 `/search` 与 `search_web` 工具使用 | +| `src/quickquip/llm/provider/gemini.py`、`rendering.py` | 声明 Gemini 内置搜索,解析 `groundingMetadata` 并渲染回复来源 | + +### 请求组装与协议处理 + +服务实现按 scope、MCP 生命周期、内置工具、SVG、定时消息、Skill、独立生成、图像预处理、健康检查、状态、自动记忆和 Agent Loop 拆分,模块清单见 `service_parts/__init__.py`。`reply_chain.py` 中的 `TurnRequestAssembler` 共用首轮与预算降级装配,输入和输出分别由 `normalize_turn_input`、`finalize_reply_text` 处理,`reply_result` 构造返回结果与触发行正文;该模块接收显式参数,与服务门面单向依赖。 + +报告输入经 `chat/period_serializer.py` 压缩:按日分节、按分钟分块,同身份连续消息用斜杠合并,复读折叠为 ×N,URL 保留域名,机器人标记为 (bot)。日报和周报将整个周期交给序列化器;月报由 `build_monthly_chat_input` 按周分配 `input_char_budget`,保留平静日,高活跃日使用剩余额度并按需等距抽稀,输出包含周节标题。 + +协议客户端的 `complete()` 对 429、5xx 和网络错误执行指数退避,策略由 `retry.py` 提供;探活和诊断使用 `RetryPolicy.disabled()` 禁用自动重试。基类、四协议实现、工厂、重试和追踪分别由相应子模块维护。 + +Gemini 在原生工具回合保存并按顺序回放含 `thoughtSignature` 的 parts。Claude 当前工具循环通过通用重建回传签名 thinking 块,已关闭且包含工具的 Loop 在归属匹配和签名有效时跨轮原生回放。Responses 使用 `store:false` 全量回放,当前工具循环回传原生 items,并检查 call_id 记账、冲突和结果配对;已关闭历史经归属校验回放 reasoning 密文,上游返回 HTTP 400 时剥离历史 reasoning 重试一次。当前工具循环内容保留。历史投影规则见第 4.2 节。 + +思考设置使用 low、medium、high、xhigh、max、ultra 六档。OpenAI Chat 按模型家族钳制;Claude 按代际选择 adaptive effort 或固定 budget 表;Gemini 按代际选择 thinkingLevel 或 thinkingBudget;Responses 使用自己的 profile 词表。Claude budget 至少为 1024,其余由映射规则照发,上游以 400 报告不支持的参数;钳制与照发决策记录节流日志。 兼容层说明: @@ -119,10 +93,10 @@ LLM 相关核心文件如下: 配置文件: -- `config/llm.toml` +- `data/llm_config.db` - 真实运行配置,本地私有 -- `config/llm.toml.example` - - 原始通用示例,保留为参考模板 +- `config/llm.json.example` + - 可导入配置数据库的 JSON 交换模板 群资料文件: @@ -159,17 +133,13 @@ LLM 默认只在以下场景触发: - `awakening_relevance` - `awakening_qa` - `awakening_fallback` -6. 如果仍未命中,则继续原有规则流: +6. 如果各唤醒条件均未命中,继续规则流: - 复读 - 接龙 - 彩蛋规则 - 时区猜测 -这意味着: - -- 默认配置下 LLM 不会吞掉普通消息 -- 规则系统依然是默认主流程 -- 显式调用优先级高于唤醒模块,唤醒模块优先级高于普通规则回复 +显式调用优先于唤醒,唤醒优先于规则回复。默认关闭唤醒时,普通群消息进入规则回复流程。 ### 3.1 唤醒模块 @@ -184,13 +154,13 @@ LLM 默认只在以下场景触发: | `awakening_boredom` | APScheduler 定时检查沉寂群,并向 opt-in 群发送低频冒泡消息 | | `awakening_fallback` | 普通消息按配置概率兜底触发 | -相关性与答疑判定会使用 `[triggers.quick_judge]` 指定的小模型配置;阈值 `>= 1.0` 时跳过对应 LLM 判定。 +相关性与答疑判定会使用 `settings.quick_judge` 指定的小模型配置;阈值 `>= 1.0` 时跳过对应 LLM 判定。 -`awakening_extend` 只由显式 LLM 入口打开,例如前缀或艾特触发。兴趣、兜底、无聊、相关性和答疑唤醒都是一次性触发,不会继续刷新延长窗口。延长窗口内仍会过滤图片-only、CQ-only、短语气词和过短无实义文本。 +`awakening_extend` 只由显式 LLM 入口打开,例如前缀或艾特触发。兴趣、兜底、无聊、相关性和答疑唤醒都是一次性触发,不会继续刷新延长窗口。延长窗口内会过滤纯图片、纯 CQ 码、短语气词和过短无实义文本。 唤醒触发会给本轮 LLM 请求附加内部触发说明,例如命中的兴趣话题或兜底触发背景,并要求模型不要暴露唤醒机制。内部说明不会作为群友原文写入 LLM 对话历史。 -被动唤醒会携带群内近期历史图片(不再只注入当前触发消息里的图片)。`awakening_extend`、`awakening_interest`、`awakening_relevance`、`awakening_qa` 和 `awakening_boredom` 携带群内近期历史图片;`awakening_fallback` 不注入图片。非视觉模型仍由 LLM 运行时的图片预处理与剥离逻辑统一处理。 +`awakening_extend`、`awakening_interest`、`awakening_relevance`、`awakening_qa` 和 `awakening_boredom` 携带群内近期历史图片;`awakening_fallback` 不注入图片。非视觉模型的图片由 LLM 运行时统一预处理或剥离。 无聊唤醒有两层开关:先在 `config/awakening.toml` 中设置沉寂秒数、概率、检查间隔和免打扰时间,再由群管理员执行 `/awakening boredom on`,写入 `data/awakening_boredom_groups.json`。 @@ -200,78 +170,85 @@ LLM 默认只在以下场景触发: ### 4.1 临时上下文 -为避免长期运行后将 24 小时持续监听数据混入模型,当前实现明确限定: +`RecentMessageBuffer` 在内存中保存当前群最近 20 条消息,默认 TTL 为 1800 秒。LLM 触发时按增量补丁规则选取需要补充的现场消息;具体游标、保底时间窗和 token 预算见下一节。缓冲内容用于当轮请求,LLM 对话历史与自动记忆由各自写入流程管理。 -- 仅在一次 LLM 触发发生时,读取**该群向前最多 20 条消息** -- 这 20 条消息来自 `RecentMessageBuffer` -- 这部分数据**仅保存在内存中** -- 不写入 `data/llm.db` -- 不作为长期记忆保存 +### 4.2 LLM 短期会话 -这是当前最重要的设计边界之一。 +**Session 身份与生命周期**:Session 表示业务层持续进行的一段 LLM 交互。群/私聊 scope 承载当前聊天,Session 身份跨轮保持;上下文纪元管理每次发送的历史窗口。内部身份为 UUIDv4,创建时间为 UTC,`SessionIdentity` 是不可变快照,`LLMRequest.session` 显式携带它。 -### 4.2 LLM 短期会话 +- 群聊首次通过输入与资格检查后懒创建,群内不同发言者共享当前 Session。显式私聊 start 创建新身份;清空结束当前身份,下一轮再创建。查看状态和被过滤的消息不创建聊天身份。 +- 工具续轮、重试、流式回退、历史裁剪、冷场、撤回、人格/模型/协议切换、禁用再启用均延续当前身份。活动身份保存于 SQLite,重启后继续使用。 +- 私聊保存结束时,将身份与历史归档;恢复保留原身份并替换当前 Session。空私聊结束不建存档;不保存结束、清空和删除存档解除相应绑定。 +- 身份、历史迁移与 generation 更新在同一写事务中完成;事务成功后清理派生缓存。每轮冻结身份及 generation,失效后停止工具续轮,并拒绝迟到结果写入或交付。无 Agent 记录路径同样使用事务写入屏障。 +- 播报、总结、周月报、人物志、触发判定、后台记忆提取、诊断与独立命令每次操作使用一个短任务身份,级联与操作内重试复用。聊天图片预处理和工具图片转述显式继承聊天身份。 + +出口投影接口为 `SessionProjector = Callable[[SessionIdentity], str]`。客户端 Profile 接收业务身份,按协议生成请求 Header、Body 或缓存键,具体契约见[请求指纹 Profile](../admin/client-fingerprints.md)。低层 `LLMRequest` 未提供身份时保留 `None`;依赖业务身份的 Profile 会在发送前拒绝缺失身份。 + +`SessionIdentity` 的可序列化字段保持身份与创建时间;`SessionModelProjection` 承载 Session 的首次使用模型绑定,运行时投影状态不进入身份 JSON。Agent schema 4 的 `session_client_models` 按 Session 与客户端原子保存模型名,持续聊天每次绑定验证当前身份及 generation;存档恢复保留绑定,清空或删除最后一份存档时清理无引用绑定。独立操作在内存中绑定,Gemini CLI 的 default 与显式模板共用同一客户端键,模板版本跟随当前冻结配置。 -工具历史投影在请求内按当前敏感词表检查完整 Loop。命中 block 或包含输出过滤替换态时,使用清洗后的文本档案并保留工具终态汇总,省略原生块、工具参数和结果正文;所有预算降级沿用该请求副本,持久化原文保持不变。未命中的 Loop 保留原有协议重放路径;原生回放(Claude 签名块 / Gemini parts / Responses output items)以 owner 五元组精确匹配为前提,失配或形状损坏按协议各自降级(档案/通用重建),Responses 侧另有跨 Loop call_id 冲突与配对完整的发送前守门。 +HTTP 追踪在列表、详情及 SSE 事件中返回可空的 `session_id`。已有追踪记录保持空值。传输层仅在当前 `complete()` 范围内绑定显式快照,退出或取消后恢复;不从外层任务上下文推断业务归属。管理页面继续使用现有展示。 -LLM 自身的问答往返会写入 SQLite,用于多轮延续。自 1.14 起读取窗口由**会话纪元**(session epoch)机制管理,取代旧的「行数滚动窗」: +升级前备份会话与追踪 SQLite 库;新增身份列为可空字段,旧消息与存档保留。回退旧代码运行后再次升级,须停服、备份并重置活动与存档绑定,使新版重新分配身份;维护 SQL、数据库路径与执行方式统一见[管理员回退与再次升级流程](../admin/llm-configuration.md#回退旧版本后再次升级)。该维护保留消息、存档、设置和 generation,追踪历史身份继续作为观测记录保存。数据库不可用时,持续聊天停止执行并返回存储错误,不分配临时身份继续回复。 -- 每个键(`群 × provider × model`)维护一个只追加的读取锚点:每次触发读取 `id >= 锚点` 的全部历史,窗口随对话增长、不逐轮位移——这是自动前缀缓存跨轮命中的结构性前提 -- 锚点只在三种时机前移:**冷场**(距该键上次 LLM 请求超过 T 秒且窗口超过 H_cold,缩回 L_cold)、**触顶**(窗口超过 cap,缩回 L_hot)或**容量降级**(请求预算超限时由服务层调用 `force_advance_to_hot` 缩回 L_hot 并重建请求重试一次,付费 miss 换优雅降级);默认 T=300s、L_cold=4k、H_cold=5k、L_hot=32k、cap=64k(token 估算),全部可在 `llm.toml` 的 `[runtime]` / `[[providers]]` 用 `epoch_*` 键调整(见 `docs/admin/configuration.md`) -- 窗口单位是 token 估算(`token_estimate.py`),不再是行数;另有 1024 行的行数硬兜底(防海量超短行撑爆 provider 的 messages 数组) -- 锚点只落在 user/assistant 对边界,且保留最少 4 行(防单条超长转发把窗口吃空) +聊天消息与 Agent 执行记录各自遵循保留策略。消息裁剪或删除时,数据库在同一事务中将相关 Turn 的 `message_row_id` 置空;Turn、工具和交付记录按执行记录的保留规则维护。显式撤回继续清理正文及原生证据,清空会话删除整个 Scope 的执行记录。业务库 Agent schema 3 在首次打开时修复已有的悬空消息引用并安装删除保护,保留现存消息、执行记录和 Session 身份。 + +工具历史投影在请求内按当前敏感词表检查完整 Loop。命中 block 或包含输出过滤替换态时,使用清洗后的文本档案并保留工具终态汇总,省略原生块、工具参数和结果正文;所有预算降级沿用该请求副本,持久化原文保持不变。未命中的 Loop 保留原有协议重放路径;原生回放(Claude 签名块 / Gemini parts / Responses output items)以 owner 五元组精确匹配为前提:渠道 ID、协议、实际请求模型、端点指纹和协议 Profile 指纹全部相同,失配或形状损坏按协议各自降级(档案/通用重建),Responses 侧另有跨 Loop call_id 冲突与配对完整的发送前守门。 + +LLM 自身的问答往返会写入 SQLite,用于多轮延续。读取窗口由**会话纪元**(session epoch)管理: + +- 每个键(`群 × provider × model`)维护一个只追加的读取锚点:每次触发读取 `id >= 锚点` 的全部历史,窗口随对话增长,锚点保持稳定以支持跨轮前缀缓存 +- 锚点只在三种时机前移:**冷场**(距该键上次 LLM 请求超过 T 秒且窗口超过 H_cold,缩回 L_cold)、**触顶**(窗口超过 cap,缩回 L_hot)或**容量降级**(请求预算超限时由服务层调用 `force_advance_to_hot` 缩回 L_hot 并重建请求重试一次);默认 T=300s、L_cold=4k、H_cold=5k、L_hot=32k、cap=64k(token 估算),全部可在 `settings.runtime` 与渠道/模型 `parameters` 的 `epoch_*` 键调整(见[配置参考](../admin/configuration.md)) +- 窗口使用 `token_estimate.py` 估算 token;1024 行硬上限同时控制消息数组长度 +- 锚点只落在 user/assistant 对边界,且保留最少 4 行,为超长消息保留基本对话配对 - 存储裁剪以该群所有纪元键的最老锚点为准;锚点缺失(进程重启后)时只按 2048 行硬上限兜底(`MAX_STORED_CONVERSATION_MESSAGES`,群聊/私聊同值),不按窗口重估删行 -- 锚点状态保存在进程内存中:进程重启 = 冷一次缓存,重启后首个请求按「距最新一条一个标准 CTX(8k token)跨度」重新懒初始化 -- `/llm context_limit ` **语义变更**:从「每次最多读取 n 条」变为「该会话(群聊/私聊均可,上限 1024 条)退化为保留最新 n 行的滚动窗」;`/llm context_limit reset` 恢复纪元自动管理。`[runtime] history_limit` 全局默认不再作为读取上限生效;`history_max_messages_per_group` 废弃(保留解析、不再生效) -- `clear_context` 三件齐清:会话消息存储、最近消息缓冲、纪元锚点(私聊会话 start/end/resume 同路径) -- `/llm use` 换 provider/model 自动开新纪元(键不同);`/llm persona use` 按冷场水位前移锚点(system prompt 字节变化 = 缓存全灭 = 免费重置窗口) -- history 渲染信任落库时定格的 `canonical_name`(渲染冻结),不再按当前身份索引重算——改名用户在前缀中保持旧名,正是冻结的目的 +- 锚点状态保存在进程内存中:进程重启后,首个请求按距最新消息约 8k token 的范围懒初始化锚点 +- `/llm context_limit ` 将群聊或私聊会话设为保留最新 n 行的滚动窗,上限 1024 行;`/llm context_limit reset` 恢复纪元管理。读取上限由会话覆盖与纪元规则决定;旧 `history_limit` 全局默认和 `history_max_messages_per_group` 保留解析,运行时忽略这两个旧值 +- `clear_context` 清理消息与 Agent 执行记录,解除 Session 绑定并推进 generation;提交后清理最近消息缓冲、纪元锚点、信封缓存和 Skill 激活登记。私聊 start/end/resume 使用相应事务与缓存清理流程。 +- `/llm use` 换 provider/model 自动开新纪元(键不同);`/llm persona use` 按冷场水位前移锚点,以适应人格提示变化后的窗口重建 +- 历史消息按落库时冻结的 `canonical_name` 渲染;用户改名后,历史前缀保留当时的名称 -**近期消息缓冲 = 【现场】补丁**:`recent_message_buffer.py` 对 LLM 请求路径不再提供全量快照,而是增量补丁(`list_patch`): +**近期消息的【现场】补丁**:LLM 请求通过 `recent_message_buffer.py` 的 `list_patch` 取得增量现场消息: 单次请求在首次读取后保存补丁快照,首轮装配与预算缩窗重建共用该快照,并按最新历史去重。缓冲游标仅在自取时推进;私聊未参与补丁与显式空补丁保持独立计量语义。近期图片继续使用独立的全量快照。 - 候选 =(上次服役之后的新消息)∪(`recent_context_floor_seconds`=300s 滑动保底窗内的消息),再按 message_id 剔除 history 已覆盖者与当前触发消息,最后从最新往回截到 `recent_context_token_budget`=800 token(估算,至少保留最新一条;非法取值回退默认并告警) - 读即服役:取出后 `note_patch_served` 推进按群游标;失败轮丢失超保底窗的旧补丁,由保底窗兜底 -- **被动唤醒的近期图不受增量语义收窄**:`include_recent_images` 路径的图片源是 `list_recent` 全量快照(TTL 窗语义,与文本补丁解耦)——无聊唤醒恰在冷场(补丁最空)时触发,图若随增量游标收窄该特性会静默失效 -- 预算只在服役侧执行:buffer 写入侧仍按 20 条 + TTL 1800s 收口(内存上界不动),estimator/budget/floor 全部由 service 按 `[runtime]` 配置注入(仅全局键,无 provider 覆盖) -- 适配层不再向 `generate_reply` 传快照;service 在群聊且未显式注入时自取(`recent_messages=[]` 显式空是测试注入口)。私聊不自取 +- `include_recent_images` 通过 `list_recent` 全量快照取图,使用 TTL 时间窗,与文本补丁游标独立 +- 预算只在服役侧执行:buffer 写入侧按 20 条与 TTL 1800 秒控制容量,estimator/budget/floor 全部由 service 按 `settings.runtime` 配置注入(仅全局键,无 provider 覆盖) +- service 在群聊且调用方未显式注入时取得补丁;`recent_messages=[]` 表示显式空输入,供测试或调用方控制现场内容。私聊跳过自取 - `list_recent` 全量快照保留给两个不适用增量语义的消费者:`context_rules` 规则引擎与「读近期消息」模型工具 **场景块消息结构**:当前 messages 数组采用“以 bot 回复为边界的场景块”模式: - 连续的多人发言归入同一 `role="user"` 场景块(bot 回复打断场景) - 所有发言者使用统一格式:`身份(QQ 号):内容` -- 场景以 `【上文】`(历史)或 `【当前提问】`(最后一轮提问)标记;现场补丁独立成 `【现场】` 段(带说明行,标识为氛围而非直接对话),尾巴顺序定型 `【轮次上下文】→【上文】→【现场】→【当前提问】` -- 无聊唤醒与定时任务是合成触发源:落库结构化配对行(`【自动唤醒】<诱因>` / `【定时消息】按 发送:<摘要>`)消除 history 的 assistant 孤行,但不从合成内容抽取自动记忆(`store_user_message` 与 `trigger_auto_memory` 双开关);合成 user_id(`boredom_timer`/`scheduled_timer`)既不进信封参与者,渲染时也直接以名字呈现(不包装成「(QQ xxx,未登记)」伪身份) +- 场景以 `【上文】`(历史)或 `【当前提问】`(最后一轮提问)标记;现场补丁独立成 `【现场】` 段(说明这些消息作为聊天氛围使用),消息尾部顺序为 `【轮次上下文】→【上文】→【现场】→【当前提问】` +- 无聊唤醒与定时任务是合成触发源:落库结构化配对行(`【自动唤醒】<诱因>` / `【定时消息】按 发送:<摘要>`)消除 history 的 assistant 孤行,但不从合成内容抽取自动记忆(`store_user_message` 与 `trigger_auto_memory` 双开关);合成 user_id(`boredom_timer`/`scheduled_timer`)既不进信封参与者,渲染时也直接以名字呈现 - 格式化仅在 `build_messages()` 组装时做一次,DB 存储原始文本(`raw_content` 列) - 引用消息会同时保留“当前提问者”和“引用发送者”,并显式区分机器人自己,避免 A 引用 B 时被误读成 B 在发言 -- 合并转发会递归展开多层节点,并保留每层的文字和图片信息,不再只剩一个占位外壳;组合文本总长封顶 4000 字符,超出在最外层出口硬切并追加「…(合并转发内容过长,已截断)」 +- 合并转发会递归展开多层节点,并保留每层的文字和图片信息;组合文本总长封顶 4000 字符,超出在最外层出口硬切并追加「…(合并转发内容过长,已截断)」 - 非视觉模型的图注以文本身份落库:落库 `raw_content` 追加 `[图片 N 张:…]`(转发图注并入转发文本),落库字节即下一轮 history 的前缀字节,转述内容不随轮丢失、前缀稳定 -这样做的好处: -- 模型只看到一种“某人说了某话”的语法,消除历史/缓冲/当前三种格式的解析负担 -- `【当前提问】` 明确标记最后一轮——模型无需自己推断该回答谁 -- 不存在 DB 存取嵌套包装(旧实现将已格式化的文本再次包入历史消息外层) +统一格式使模型可以按发言者理解历史、现场和当前提问;`【当前提问】` 指定需要回答的最后一轮。格式化发生在请求组装阶段,数据库保存原始正文,避免重复包装。 ### 4.3 图片输入边界 图片理解遵循显式触发和受限被动唤醒规则: -- 必须和 `/ai` 或 `@机器人` 同时出现 -- 单次最多处理 5 张当前、引用图片与近期上下文图片;转发图片不再作为图片本体附带(视觉模型同样不附),只以文字/图注形式进入 -- 被动唤醒在 `awakening_extend`、`awakening_interest`、`awakening_relevance` 和 `awakening_qa` 中携带群内近期历史图片 +- 群聊当前图片通过配置前缀或艾特机器人触发;被动唤醒按下述规则补充近期图片 +- 单次最多处理 5 张当前、引用图片与近期上下文图片;转发图片以文字或图注形式进入请求 +- `awakening_extend`、`awakening_interest`、`awakening_relevance`、`awakening_qa` 和 `awakening_boredom` 携带群内近期历史图片 - 近期历史图片使用当前请求剩余的图片名额,并优先保留最新图片 -- 单张图片(解码后)上限 5MB;发送前统一过内联媒体收口(`provider/media_guard.py`):GIF 按魔数嗅探自动取首帧转 PNG(各家模型对动图的实际口径为拒收或仅首帧,转码无能力损失)、同请求内相同内容去重、MIME 按实际字节归一,并对全部图片施加解码字节总量预算(默认 5MB,provider 级 `max_inline_media_bytes` 覆盖,0 = 不限)。超出单图上限或剩余额度的图片先降采样重编码(EXIF 方向校正、透明平铺白底、原始尺寸优先 + 长边 2560→768 阶梯 × JPEG q85/q70 两档,命中即停;结果按「原始字节哈希 + 目标额度」缓存),额度低于 96KB 不再压缩。压缩后仍装不下的按候选优先级前缀止停:第一张连同其后全部跳过并记日志,避免丢弃当前大图却保留后续无关小图 -- provider 图片下载按客户端实例缓存(TTL 10 分钟、容量 32 张 LRU,仅缓存成功结果):同一轮内工具循环重建请求与退避重试不再重复下载同一 URL;GIF 首帧转码结果按内容哈希缓存,逐轮序列化不重复解码 +- 单张图片(解码后)上限 5 MiB;发送前统一过内联媒体收口(`provider/media_guard.py`):GIF 按魔数嗅探自动取首帧转 PNG、同请求内相同内容去重、MIME 按实际字节归一,并对全部图片施加解码字节总量预算(默认 5 MiB,provider 级 `max_inline_media_bytes` 覆盖,0 = 不限)。超出单图上限或剩余额度的图片先降采样重编码(EXIF 方向校正、透明平铺白底、原始尺寸优先 + 长边 2560→768 阶梯 × JPEG q85/q70 两档,命中即停;结果按「原始字节哈希 + 目标额度」缓存),额度低于 96 KiB 不再压缩。压缩后仍装不下的按候选优先级前缀止停:第一张连同其后全部跳过并记日志,避免丢弃当前大图却保留后续无关小图 +- provider 图片下载按客户端实例缓存(TTL 10 分钟、容量 32 张 LRU,仅缓存成功结果):同一轮内工具循环重建请求与退避重试复用同一 URL 的成功下载结果;GIF 首帧转码结果按内容哈希缓存,逐轮序列化不重复解码 - 请求组装先统一准备用户消息与各批工具结果图片,共享字节预算和内容去重;优先最新用户消息中的当前/引用/近期图片,再按新到旧处理工具结果与历史图片。预算耗尽后停止接纳后续低优先级图片,重试和并发请求各自创建预算。协议序列化保留完整工具结果批次与原消息顺序 - 如果只有图片没有文字提示,会自动补一个默认识图提示 -- 视觉主模型直接接收原图;列入 `non_vision_models` 的主模型接收带来源和序号的视觉转述 +- 视觉主模型直接接收原图;模型条目 `vision = false` 的主模型接收带来源和序号的视觉转述 - 前置视觉识别不可用、返回空内容或任一图片识别失败时,本轮终止并提示用户重试 MCP 工具也可返回经过校验的内联图片。它们不写入对话数据库、普通日志或 MCP 状态;视觉模型在下一轮工具调用消息中接收图片,非视觉模型仅接收经过二次敏感词扫描的转述文本。工具图片的转述不可用或失败时,Agent Loop 继续使用安全工具文本,而不会把原图或编码降级为文本。 -模型产出的图片(Responses 内置 image_generation 工具条目——codex 类后端会在服务端注入,请求未声明也会出现;以及 Gemini 响应的 inlineData 图片 parts)由各协议适配器提取为归一的响应侧 `generated_images` 附件,并从原生回放批次剥除:base64 不进 native_blocks,回放无收益纯成本。工具循环在每轮响应到达时把附件收进外发图片通道——与 draw_svg 等工具路径同通道、同上限(`MAX_OUTBOUND_TOOL_IMAGES`)、同「后续调用失败不丢弃已产出图片」语义,送达由适配层拼在正文后发送;收集侧挂与 svg_render 同风格的独立限流(全局 10 次/分钟、单用户 2 次/分钟)。 +模型产出的图片(Responses 内置 image_generation 工具条目——codex 类后端会在服务端注入,请求未声明也会出现;以及 Gemini 响应的 inlineData 图片 parts)由各协议适配器提取为归一的响应侧 `generated_images` 附件,并从原生回放批次剥除,base64 不进入 native_blocks。工具循环在每轮响应到达时把附件收进外发图片通道——与 draw_svg 等工具路径同通道、同上限(`MAX_OUTBOUND_TOOL_IMAGES`)、同「后续调用失败不丢弃已产出图片」语义,送达由适配层拼在正文后发送;收集侧挂与 svg_render 同风格的独立限流(全局 10 次/分钟、单用户 2 次/分钟)。 ### 4.4 语音输入边界 @@ -282,19 +259,11 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 - 若 OneBot 协议端的 `record` 段已经包含 `text` / `transcript` / `transcription`,直接使用该文本 - 否则通过 OneBot `get_record` 获取音频文件,并调用 `config/generation.toml` 中 `[asr]` 配置的 provider - 转写结果会作为 `[语音转文字:...]` 拼入当前用户消息,并进入最近消息、日报/播报采集和词云输入 -- ASR 失败时不阻塞原消息处理;没有可用转写时按原有文字/图片输入逻辑继续 +- ASR 失败时不阻塞原消息处理;没有可用转写时按文字和图片输入逻辑继续 ### 4.5 长期记忆 -长期记忆当前来源非常保守: - -- 人工 `/remember` -- 自动记忆抽取开启时,仅从 LLM 已触发会话内提取稳定事实 - -明确不允许: - -- 直接把 24 小时全群监听内容塞进记忆 -- 把所有群聊消息无差别持久化给 LLM 模块 +长期记忆由人工 `/remember` 和自动事实抽取写入。自动抽取开启时,从已触发的 LLM 会话提取稳定事实;普通群聊归档由聊天记录系统维护。 --- @@ -317,7 +286,7 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 ### 5.2 群风格约束 -这部分不靠整份群资料硬灌,而是抽取稳定特征: +人格提示从群资料中选择稳定的交流特征: - 熟人化 - 深夜活跃 @@ -327,27 +296,11 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 ### 5.3 词表按需注入 -`vocab.yaml` 不会整份注入模型。 - -当前做法是: - -- 只有当 prompt 命中某个别名或黑话 -- 才在当轮 user 消息头部的【轮次上下文】信封里追加一小段消歧说明(system prompt 已静态化,见下) - -例如: - -- `哈基镜` 通常指镜子 -- 注意不要和王者荣耀的镜混淆 - -这样做的好处: - -- 模型更会“听懂” -- 不会变成背词表机器 -- 不容易把群资料污染成固定口癖 +当输入命中 `vocab.yaml` 中的别名或黑话时,运行时在当轮 user 消息头部的【轮次上下文】信封追加对应的消歧说明。例如某个昵称指向群成员或物品时,只注入相关解释。system prompt 保持稳定,词表按实际命中参与当轮理解。 ### 5.4 Provider 风格覆盖 -每个 `[[providers]]` 条目支持可选字段 `style_overrides`(多行字符串)。 +渠道的 `providers[].parameters.style_overrides` 支持多行字符串;模型的 `models[].parameters.style_overrides` 可覆盖该值。共享风格段通过同一 `parameters` 对象中的 `style_profile` 引用。 此字段的内容会在每次调用该 provider 时,追加到 persona 的 `style_prompt` 之后,用于修正特定模型的口癖。 @@ -357,7 +310,7 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 - DeepSeek:禁止分点列举 - Claude / Gemini:禁止旁白括号、禁止过于简略的回复 -修改后需 `/llm reload` 生效。 +通过 Web Admin「LLM 配置 → 渠道与模型」保存草稿,再单独应用并确认 Bot 已加载目标版本;在途任务保持原快照,后续请求使用新风格。`/llm reload` 读取已发布配置。完整参数继承与交换流程见[LLM 配置管理](../admin/llm-configuration.md)。 ### 5.5 身份映射注入 @@ -365,9 +318,9 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 标识符分层:**LLM 层认人以标准身份(名字)为主锚**,QQ 号作为名字后的常驻后缀(区分同名无档案成员);代码层(at 段解析、身份索引配对、存储列、注入管理)一律以 QQ 号为唯一键。`identities.yaml` 是 canonical name 的权威源,`vocab.yaml` 的标准名属称呼提示层,两处命名须保持同名对齐。群级合并仅对纯数字 `group_id` 生效:空串或非数字 scope(如私聊复合 id)不加载群级文件,`group_identities` 直接返回全局索引。 -当前做法是: +身份解析与消息交付遵循以下规则: -- **统一发言者格式**:所有进入 LLM 的消息(历史、缓冲、当前提问)均使用同一格式 `身份(QQ 号):内容`,不再区分三种不同的包装语法 +- **统一发言者格式**:历史、现场和当前提问均使用 `身份(QQ 号):内容` - 提问者进入 LLM 时,按 QQ 号解析标准身份;认人规则教模型**名字优先**、QQ 号仅作同名区分 - 最近群聊上下文中的发言者也会按 QQ 号显示标准身份 - 消息中的艾特在**入口 ingestion 时**按**群合并身份索引**渲染为 `@标准身份`(引用消息、合并转发子消息同索引);未登记成员经群成员名片缓存(`get_group_member_info`,按 群×QQ 带 TTL)退化为 `@当前群名片`,查询失败才回退 `@QQ 号` 数字形态——名片预取覆盖消息顶层 @;引用/转发子消息内未登记 @ 不做名片预取,仅有段自带名称或与顶层重叠的预取名片时降级使用,否则回退数字形态 @@ -375,75 +328,32 @@ MCP 工具也可返回经过校验的内联图片。它们不写入对话数据 - **艾特档案注入(信封段)**:被艾特但未在窗口内发言的登记成员,其标准身份+别名+备注随当轮【轮次上下文】信封注入(名字在前、QQ 作配对键,上限 5 条);候选来自入口结构化采集(at 段 QQ)与对窗口文本的 `@QQ 数字` 扫描(覆盖冻结落库的存量形态),已在窗口带发言人标签者跳过 - **出站艾特还原**:模型回复文本中的 `@QQ 号` 数字形态在发送出口(`_llm_reply.py`)切分为真实 at 段(右边界防幻觉长号);群级开关 `llm_at_mention`(默认开)另启用「@名字」通道——按群合并身份快照的标准身份/别名/观察名片做最长匹配,歧义、艾特 bot 自身或未登记名字一律保留为纯文本(at-all 结构上不可达),群聊 LLM 路径(显式/被动/无聊唤醒/定时任务)共用同一解析出口 - **艾特护栏**:单条回复真实艾特上限 3 个(两通道合计,超出降级为文本);「@名字」通道对同一(群 × QQ)有 `mention_cooldown_seconds` 冷却(默认 600 秒,0 关闭)——冷却在送达确认(可信 message_id)后记账、解析侧只读,发送失败不消耗名额,窗内重复艾特降级为文本 -- **引用锚定**:群级开关 `llm_quote_reply`(默认开)让群聊 LLM 回复以引用段锚定触发它的那条消息(机械化接线,LLM 无感);协议端显式拒绝引用段时去段重发正文,超时等传输异常不重试 +- **引用锚定**:群级开关 `llm_quote_reply`(默认开)让群聊 LLM 回复以引用段锚定触发它的那条消息;协议端显式拒绝引用段时去段重发正文,超时等传输异常不重试 - **周期报告读时重解析**:日总结/周报/月报/每日播报的序列化输入在读取时按身份索引把登记成员渲染名换成标准身份(归档仅存 user_id,无需回填);同名不同 QQ 碰撞时给碰撞者附 QQ 后缀(碰撞触发式,控制压缩文本体积) -- **身份信息只在 messages 中呈现**:system prompt 不再重复声明“当前提问者是谁”——消除双信息源冲突 -- **system prompt 完全静态化(前缀缓存契约)**:当前时间/星期、节日提示、对话参与成员、持久记忆、词表命中等逐轮变化的内容一律只在当轮 user 消息头部的【轮次上下文】信封呈现(组装时渲染、不落库),system 跨轮、跨日字节稳定,自动前缀缓存可跨轮命中。信封 token 经 `envelope_meter` 落 `envelope_tokens` 列进用量账本:Agent Loop 内每行同值,看板只按 **AVG** 解读为每轮成本,**禁止 SUM**(同回合重复计) +- **发言者身份**:messages 标记当前提问者与各轮发言者,system prompt 维护稳定的人格与行为要求 +- **静态前缀与当轮信封**:时间、星期、节日、参与者、记忆和词表命中在当轮 user 消息头部的【轮次上下文】信封呈现,组装时渲染。同一配置、人格和工具集合下,system 跨轮、跨日字节稳定,支持前缀缓存。`envelope_meter` 将信封 token 记入 `envelope_tokens`;同一个 Agent Loop 的各行记录相同值,看板用 **AVG** 统计每轮成本,求和会重复计数 - **加载可观测**:`identities.yaml` 缺失(INFO)/存在但无有效条目(WARNING)/正常加载条目数(INFO)均有日志;空模板与缺失在索引层面等价 -这样可以减少群友频繁改名带来的身份漂移,并且让模型在单一信息源中自然识别发言者归属。 +身份索引负责当前成员解析,冻结历史保留记录时名称;消息标签和当轮信封分别承载发言归属与当前资料。 --- ## 6. 配置说明 -### 6.1 `config/llm.toml` - -主要区块: - -- `[runtime]` - - `enabled` - - `memory_enabled` - - `default_provider` - - `default_persona` - - `history_limit` - - `history_max_messages_per_group` - - `memory_limit` - - `memory_max_items_per_group` - - `max_prompt_chars` - - `tool_calling_enabled` - - `tool_max_rounds` - - `tool_max_calls_per_round` - - `auto_memory_enabled` - - `auto_memory_prompt` - - `auto_memory_max_tokens` - - `agent_delivery_intermediate_enabled` / `agent_delivery_final_enabled`(Agent Loop 分段交付两域的全局默认:中间轮发送与最终轮分段;旧键 `agent_delivery_enabled` 未删除,读取时按两域同值映射) -- `[triggers]` - - `default_prefix` - - `allow_prefix` - - `allow_at` - - `empty_prompt_reply` - - `[triggers.quick_judge]`:唤醒模块和语境规则使用的快速判定模型 -- `[tools]` - - `enabled` - - `enabled_mode`:`enabled` 非空时的作用方式,`append`(默认,默认白名单 + MCP 之上追加)/ `replace`(精确过滤) - - `discovery_mode` - - `discovery_min_tools` - - `discovery_search_limit` - - `discovery_max_loaded_tools` - - `always_loaded` -- `[[providers]]` - - `id` - - `protocol` - - `base_url` - - `api_key_env` - - `default_model` - - `models` - - `timeout_seconds` - - `temperature` - - `max_output_tokens` - - `style_overrides`(可选,追加到每次调用的 system prompt 末尾) - - `auth_method`(可选,`api_key` / `bearer`,默认 `api_key`;Claude 控制 `x-api-key` / Bearer,Gemini 控制查询参数 key / Bearer) - - `prompt_caching`(可选,`claude` 协议专用,启用 Anthropic Prompt Caching) - - `cache_ttl`(可选,`claude` 协议专用,`"1h"` 启用 1h 扩展缓存、留空=默认 5min;仅 `prompt_caching` 开启时生效) -- `[daily_briefing]` - - 每日早/午/晚播报全局开关、三段 cron、最小消息数、活跃用户/热词/样本上限、上下文规模、输出长度、模型级联列表 -- `[daily_summary]` - - 每日总结全局开关、生成/发布 cron、最小消息数、字数目标、模型级联列表 - -`[runtime]` 的完整键集(会话纪元 `epoch_*`、重试退避、请求/重放预算、回复分段、Loop 记录等)以 [../admin/configuration.md](../admin/configuration.md) 为准;本文 §4.2 详述纪元与预算机制。 - -Persona 定义已从 `llm.toml` 移出,改为 `config/personas/` 目录下每个 `.toml` 一个人格文件,`_shared.toml` 存储共享行为准则与风格规则。 +### 6.1 `data/llm_config.db` + +配置服务保留草稿、不可变发布快照、修订号、应用任务与 Bot 心跳。Web 和 CLI 共用校验与修订检查,Bot 以租约领取控制任务;保存草稿不会修改运行时,重启加载已发布版本。每个 Agent Loop 和独立报告任务固定配置、价格、工具与资源直到结束。 + +交换格式的主要字段: + +- `providers`:有序渠道、模型与别名;固定认证/地址字段在渠道层,请求参数在渠道或模型的 `parameters`。 +- `style_profiles` 与 `pricing`:共享风格及价格,价格以 `(渠道 ID, 配置的模型 ID)` 精确匹配请求模型名,不跨渠道兜底。 +- `routes`:四类报告的有序候选,以 `{provider_id, model}` 引用;`@default` 动态选择默认模型。 +- `settings.runtime` / `triggers` / `auto_search` / `quick_judge` / `image_preprocessing`:全局运行、触发与专用模型选择。 +- `settings.tools` / `skills` / `mcp`:工具绑定、Skill 资源限额与 MCP 配置。 +- `settings.daily_summary` / `daily_briefing` / `weekly_report` / `monthly_report`:报告开关、提示与时间配置。 + +完整字段见 [配置参考](../admin/configuration.md),状态和操作契约见 [配置 API](llm-configuration-api.md)。`config/personas/` 和 `config/generation.toml` 继续维护独立配置;旧 TOML 仅供迁移与迁移前版本回退。配置修订号覆盖配置库,外部文件和环境变量保持独立生命周期。 ### 6.2 工具发现 @@ -455,27 +365,23 @@ Gemini 3 原生工具回合把 `thoughtSignature` 视为不可解释、不可重 实现细节见 [tool-discovery.md](tool-discovery.md),MCP 大工具集场景见 [mcp-integration.md](mcp-integration.md)。 -注意: - -- 这里的配置是“逻辑配置” -- 真正的硬上限仍然在代码里存在 -- 即使把 `history_max_messages_per_group` 写大,实际仍会被代码上限截断 +窗口的 token 预算和行数上限见第 4.2 节;工具发现的数量约束由 `settings.tools` 与运行时加载规则执行。 ### 6.3 Skill 系统 -Skill 系统是 1.16 引入的运行时可扩展能力:部署者把 Skill 包(一个子目录一个 Skill:`SKILL.md` 指令正文 + 可选 `references/` 参考资料 + 可选 `scripts/` 脚本)放入 `skills/` 目录,已安装 Skill 的描述清单常驻系统提示,AI 遇到匹配的请求时自行激活,按需读取资料、检索内容或执行脚本后作答。未部署任何 Skill 时工具不注册、系统提示不变,实例行为与此前完全一致。 +部署者将 Skill 包放入 `skills/`,每个子目录包含一份 `SKILL.md`,并可附带 `references/` 资料和 `scripts/` 脚本。Skill 开关开启且目录存在有效条目时,运行时注册 Skill 工具;工具调用总开关开启后,描述清单进入系统提示。AI 根据请求激活 Skill,再按需读取资料、检索内容或执行脚本。 运行时结构(`llm/skills/` 域包,文件级清单见 §2): - `parser`:SKILL.md 校验(name 命名约束与长度、description 长度、包体积上限) -- `catalog`:`skills/` 目录扫描——每轮请求现扫、改动零延迟生效(无缓存失效问题);路径加固把读取与检索限制在 Skill 目录内,内容按 SHA-256 复验,扫描容忍目录被并发修改 +- `catalog`:`skills/` 目录每轮请求扫描,下一轮使用新的目录内容;路径加固把读取与检索限制在 Skill 目录内,内容按 SHA-256 复验,扫描容忍目录被并发修改 - `state`:按会话维护激活状态登记,激活随上下文生命周期保持一致(`/llm clear_context` 等清理同步生效) - `context`:catalog 块与激活标记的文本渲染(模型可见面的唯一出口,纯函数无状态) - `preset_drift`:预置 Skill 漂移检测——`skills/` 与 `skills.example/` 同名目录的全文件字节级指纹比对(只读);分叉名单变化时记 WARNING,`/skill list` 标注分叉条目,结果只进日志与命令回复,不进 catalog 块与系统提示;`skills.example/` 缺失时 fail-open。主机侧同步脚本 `scripts/sync_preset_skills.py` 按文件路径装载同一实现(stdlib,无 venv 依赖) - `tools/`:四枚工具——`activate_skill`(激活)、`read_skill_resource`(读资料,字节上限)、`search_skill_resources`(内容检索,病态正则拒绝)、`run_skill_script`(脚本执行:隔离最小环境、无 shell、环境变量白名单、工作目录固定、超时与输出上限) - `service_parts/skills.py`:描述清单注入系统提示(预算 `catalog_max_bytes`,实际取 min(模型上下文窗口 2%, 此值))与激活接缝;敏感词联动——Skill 描述命中 block 词表时整只剔除该 Skill,激活注入文本预扫命中时本次不登记 -配置集中在 `llm.toml` 的 `[skills]` 段(键与默认值见 [../admin/configuration.md](../admin/configuration.md));群友侧用 `/skill list` 查看已安装与已激活项。部署方式与安全模型见 [../admin/skills.md](../admin/skills.md),编写自己的 Skill 见教程 [skill-tutorial.md](skill-tutorial.md)。 +配置集中在 `settings.skills`(键与默认值见 [../admin/configuration.md](../admin/configuration.md));群友侧用 `/skill list` 查看已安装与已激活项。部署方式与安全模型见 [../admin/skills.md](../admin/skills.md),编写自己的 Skill 见教程 [skill-tutorial.md](skill-tutorial.md)。 ### 6.4 `config/awakening.toml` @@ -506,13 +412,13 @@ interest_topics = ["关键词"] ### 6.5 `.env` -本地开发与容器运行都需要: +在根目录 `.env` 配置实际使用的渠道凭据;公开模板中的变量名示例为: - `OPENAI_API_KEY` - `ANTHROPIC_API_KEY` - `GEMINI_API_KEY` -此外容器部署还会用到: +OneBot 连接与 NoneBot 启动按部署模板配置,相关变量包括: - `QQ_ACCOUNT` - `ONEBOT_WS_URLS` @@ -533,9 +439,9 @@ LLM 相关的多模态输入/产出配置在 `generation.toml` 中维护: ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /audio/transcriptions`。配置示例见 `config/generation.toml.example`。 -`draw_svg` 是内置工具但**不在默认启用名单**:需要在 `generation.toml [svg]` 设 `enabled = true`,并在 `llm.toml [tools] enabled` 中加入 `"draw_svg"`。渲染由 `quickquip.generation.svg` 编排——输入硬约束与静态清洗(`svg_sanitize.py`)、输出尺寸服务端覆盖(剥离根节点 width/height 后按 viewBox×2 显式传参)、spawn 子进程沙箱(Linux 带 RLIMIT_AS/RLIMIT_CPU,墙钟超时兜底)。工具结果图片经 `ToolExecutionContext.outbound_images` 外发通道直接发给用户(不回喂模型),单次回复上限 3 张,渲染限流为全局 10 次/分钟、单用户 2 次/分钟。 +`draw_svg` 是内置工具但**不在默认启用名单**:需要在 `generation.toml [svg]` 设 `enabled = true`,并在 `settings.tools.enabled` 中加入 `"draw_svg"`。渲染由 `quickquip.generation.svg` 编排——输入硬约束与静态清洗(`svg_sanitize.py`)、输出尺寸服务端覆盖(剥离根节点 width/height 后按 viewBox×2 显式传参)、spawn 子进程沙箱(Linux 带 RLIMIT_AS/RLIMIT_CPU,墙钟超时兜底)。工具结果图片经 `ToolExecutionContext.outbound_images` 外发通道直接发给用户(不回喂模型),单次回复上限 3 张,渲染限流为全局 10 次/分钟、单用户 2 次/分钟。 -两层可选安全防护:`harden`(默认启用)控制第一层渲染硬防线(输入约束+清洗+尺寸覆盖+沙箱 rlimit);`content_judge`(默认关闭)控制第二层内容裁决,复用 `[triggers.quick_judge]` 的廉价模型对图片可见文本做安全判定,判定失败 fail-open。详见 `config/generation.toml.example` 中 `[svg]` 段注释。 +`harden` 默认启用,控制输入约束、清洗、尺寸覆盖和子进程资源限制。`content_judge` 默认关闭,开启后复用 `settings.quick_judge` 模型检查图片中的可见文本;判定服务失败时继续渲染。完整设置见 `config/generation.toml.example` 的 `[svg]` 注释。 --- @@ -550,11 +456,11 @@ ASR 当前支持 `openai_transcriptions` 协议,即 OpenAI-compatible `POST /a - `/llm health [verbose|detail|full]` - 运行 LLM 健康检查(llm_config、provider、database、knowledge_files、persona、tools、mcp、search、sensitive_filter、generation、image_preprocessing、runtime_bindings、auto_memory 共 13 项) - `/llm reload` - - 仅管理员。重载 LLM 配置,并探活当前会话实际生效的 provider/model + - 仅管理员。重载已发布 LLM 配置,并探活当前会话实际生效的 provider/model;草稿通过管理后台或 CLI 单独应用 - reload 后探活会发一条 max_tokens=1 的真实请求,可能产生 provider 计费;api_key 未设置时自动跳过 - `/llm probe` - 仅管理员。并发探活所有 provider(每个发一条 max_tokens=1 的请求),报告可达性与延迟 - - 每次调用都可能产生 provider 计费——按需触发,不静默扣费;api_key 未设置的 provider 自动跳过 + - 每次调用可能产生 provider 计费;api_key 未设置的 provider 自动跳过 ### 7.2 provider / model / persona @@ -614,7 +520,7 @@ LLM 侧的联网搜索有两条互斥路径: 部署要点: -- `config/llm.toml` 应在运行环境中提供 +- 初始化/迁移准备共享 `data/llm_config.db`,Bot 启动加载已发布快照,草稿通过独立任务应用 - `config/generation.toml` 启用 ASR 时需要配置可用的 `[asr]` provider - `llm_about` 应在运行环境中提供 - 包括全局 `vocab.yaml` / `identities.yaml` 与可选群级覆盖目录 @@ -633,55 +539,54 @@ LLM 侧的联网搜索有两条互斥路径: --- -## 9. 现阶段已知边界 +## 9. 数据作用域与安全处理 -当前模块定位为刻意收边的群聊 LLM,边界如下: +群聊和私聊以各自 scope 管理会话状态与记忆。自动记忆从已触发会话抽取,群资料按人格设计和词表命中使用。工具来源按第 6 节的配置与注册职责接入。 -- 不自动扫全群消息做长期记忆 -- 不自动做复杂摘要归档 -- 不做跨群共享人格状态 -- 不把 `群聊简介和概况.md` 全文直接注入模型 -- 不默认把所有外部工具都改成 MCP -- 敏感词表更新会改写 history 行的当轮渲染字节(加载时以当前词表重 scrub,不回写存储),使该轮前缀缓存 miss——安全优先的刻意取舍 +历史消息在读取时按当前敏感词表清洗,持久化原文保持不变;过滤结果变化会改变本轮请求字节,从而影响前缀缓存。完整 Loop 的敏感内容与原生回放规则见第 4.2 节。 -注:每日总结(`daily_summary`)模块已实现模型级联策略,生成失败时自动降级到下一个 provider/model,顺序在 `[daily_summary] model_cascade` 中配置。这是总结生成专用的级联,不影响普通 LLM 对话的 provider 选择。 - ---- +总结生成按 `routes.daily_summary` 的候选顺序级联,失败时尝试下一模型;普通聊天由当前会话选择的渠道与模型执行。 ## 10. 上线前建议检查项 -如果准备正式上线,建议确认: - -- `config/llm.toml` 中默认 provider、model、persona 正确 -- `.env` 中 Gemini / OpenAI / Claude key 正确 -- `/llm current` 输出正常 -- `/llm memory status` 输出正常 -- `/llm clear_context` 可用 -- `@机器人` 和 `/ai` 触发都可用 -- 关闭记忆注入后,模型仍能正常回复 -- Docker 容器内日志没有出现: - - 配置文件缺失 - - API key 缺失 - - `vocab.yaml` 缺失 - - `identities.yaml` 缺失或为空模板(日志关键字:`身份资料文件`;正常加载会输出 `已加载 N 条身份`) +部署检查以实际启用的能力为对象: ---- +- 在管理后台确认默认渠道、模型、人格与 Bot 加载版本,根目录 `.env` 配置这些渠道需要的凭据。 +- 用 `/llm current` 与 `/llm memory status` 查看当前会话设置,验证配置前缀和艾特入口,以及 `/llm clear_context`。 +- 按部署所需验证记忆开关、工具、识图或语音入口;记忆关闭时聊天按正常对话流程回复。 +- 查看配置、凭据和群资料加载日志。`identities.yaml` 缺失或为空时按默认身份解析;需要标准身份时配置资料,并确认“已加载 N 条身份”日志。 + +## 11. 请求结构排障 + +排查请求失败、设置生效或跨轮连续性时,先确认任务使用的配置,再核对最终发送载荷。配置保存、应用和 Bot 实际加载状态见 [LLM 配置管理](../admin/llm-configuration.md);参数继承与整体覆盖见该文档的“模型参数与引用”;请求特征及受管字段见 [请求指纹 Profile](../admin/client-fingerprints.md)。 + +### 生命周期与生效边界 + +- **静态前缀与当轮信封**:system 包含人格、风格和工具说明;时间、节日、参与者、记忆和词表命中进入当轮信封。同一配置、人格和工具曝光面下保持前缀稳定。配置、人格、Skill 目录或工具曝光面变化时,按对应变更解释前缀差异。实现与计量见 §5.5 的前缀缓存契约。 +- **配置与群覆盖**:LLM 任务持有开始时的配置和资源快照;已经进入生成的任务继续使用捕获的群设置,群覆盖修改后的新请求读取新设置。群覆盖、人格文件与 Skill 内容分别管理,配置修订号只覆盖配置库。检查群设置时同时核对实际请求的渠道、模型和人格。 +- **Session、纪元与追踪**:业务 Session 表示持续互动,纪元表示当前上下文窗口;重试和工具续轮沿用所属业务身份。请求追踪按逻辑调用和实际 HTTP 尝试分别建立标识。各 Profile 的字段与生命周期见本页“请求指纹与身份投影”和 Profile 文档。 +- **当前工具回合与已关闭历史**:当前回合的原生块、签名、调用 ID 和结果配对须完整。已关闭历史按 owner、完整性及预算决定原生回放或通用投影。Responses 收到上游 HTTP 400 且存在可剥离的历史 reasoning 时重试一次,保留当前回合内容;网络、鉴权、非 400 和协议终态错误使用各自错误处理路径。 + +### 核对顺序与证据 -## 11. 推荐维护方式 +1. 记录代码 SHA、安装版本、配置修订与 Bot 实际加载状态,注明操作发生在旧任务开始前还是开始后。分别确认全局默认、群覆盖、模型参数和实际选中的人格。 +2. 记录协议、Provider、请求模型、`client_fingerprint` 与 `responses_profile`。渠道上的 `extra_body`、Profile 的策略和受管字段按各适配器契约共同形成最终载荷;检查实际请求,确认覆盖字段的位置、类型和效果。 +3. 在 Web Admin 的 LLM Trace 中核对该 Loop 的实际 Header、请求正文、HTTP 状态或流终态,区分首轮、工具续轮、重试和备用地址。Trace 采集须在复现前开启;未采集的历史请求无法补回。HTTP Trace 与模型执行记录的原生内容分别服务传输取证和历史回放。 +4. 对跨轮问题记录 owner 校验、预算投影和降级原因,同时核对工具声明、结果与签名。切换模型后出现历史投影变化时,将其与当前回合的协议错误分别定位。 +5. 对照 Trace 和用量记录核实实际 HTTP 次数。Responses 的历史降级重试会形成失败与重试各自的用量记录;基座内部重试沿用其既有计量口径。配置任务提交成功、Bot 加载成功与模型请求成功分别由对应结果确认。 -后续如果继续演进,建议遵守下面的顺序: +公开复现使用脱敏样本,保留定位所需的字段结构、顺序、错误和生命周期信息;真实凭据、账号、用户正文及签名或密文仅在受保护的排障环境中核对。真实渠道结论注明具体模型、端点、Profile、代码基线和验证日期。本地替身测试记录具体覆盖的客户端契约。 -1. 先改 `config/llm.toml` 和 persona 文案 -2. 再改 `identities.yaml` -3. 再改 `vocab.yaml` -4. 最后才考虑扩大自动记忆能力 +## 12. 推荐维护方式 -原因很简单: +按问题归属选择修改入口: -- 人格问题,优先改 prompt -- 认人问题,优先改身份词表 -- 称呼理解问题,再改话题词表 -- 工具边界问题,优先改 `[tools]` 配置和注册表 -- 记忆问题,最后改自动抽取逻辑 +| 问题 | 维护入口 | +|---|---| +| 人格与交流风格 | `config/personas/` 和渠道/模型风格参数 | +| 成员身份解析 | `llm_about/identities.yaml` | +| 昵称与黑话理解 | `llm_about/vocab.yaml` | +| 工具可见范围与执行契约 | `settings.tools` 和工具注册表 | +| 记忆事实提取 | 自动抽取提示与实现 | -不要反过来。 +数据库设置通过草稿保存和独立应用生效;人格与资料文件按各自重载方式加载。先核对设置、输入和实际请求,再修改相应领域实现。 diff --git a/skills.example/self-docs/references/docs-dev-mcp-integration.md b/skills.example/self-docs/references/docs-dev-mcp-integration.md index 80d1c4f1..1c4b1be5 100644 --- a/skills.example/self-docs/references/docs-dev-mcp-integration.md +++ b/skills.example/self-docs/references/docs-dev-mcp-integration.md @@ -2,223 +2,116 @@ # QuickQuip MCP 集成说明 -## 当前状态 +本文面向维护 LLM 工具链和部署 MCP 的开发者,说明工具来源、运行时资源、部署选择与结果处理契约。接入步骤见[管理员指南](../admin/mcp-servers.md),协议入门与完整配置字段见[MCP 教程](mcp-tutorial.md)。 -项目当前已经具备以下 MCP 能力: +配置示例为导出 JSON 中的字段片段;请在 Web Admin「LLM 配置」中编辑,或修改完整导出文件后预览导入、保存草稿并单独应用。完整交换格式见 [LLM 配置管理](../admin/llm-configuration.md)。 -- `config/llm.toml` 内可直接声明 `[[mcp.servers]]` -- 支持 `stdio`、`docker`、`http`、`sse` 四种 transport -- 配置值支持 `${ENV_VAR}` 与 `${ENV_VAR:-default}` 展开 -- 启动时自动发现 MCP tools,并桥接到现有 `ToolRegistry` -- `/llm mcp status` 可查看当前 server 装载结果 +## 1. 配置归属 ---- - -## 1. 目标边界 - -QuickQuip 把 MCP 作为项目自己的工具后端来源之一,而不是把 Codex 的本地运行方式原样搬进云端。 +QuickQuip 的 MCP client 发现外部服务工具,并将它们桥接到与内置工具共用的 `ToolRegistry`。项目 MCP 配置保存在 `data/llm_config.db` 的 `settings.mcp` 中。 -这两者不是一回事: - -- Codex 的 `config.toml` 是 Codex 自己的 MCP client 配置 -- QuickQuip 需要的是项目内的 MCP client / tool backend 集成 - -因此,远程服务器不需要安装 Codex。 +Codex 的 `config.toml` 属于 Codex 宿主,包含它自己的环境、路径与信任设置。部署 QuickQuip 时,使用项目配置服务和下文的传输实现。 --- -## 2. 当前推荐路线 +## 2. 工具来源选择 -当前项目已经具备标准化工具调用能力,推荐优先级如下: +按工具职责选择实现来源: -1. 内建工具(get_identity、list_memories 等)继续走本地实现 -2. `search_web` 硬编码走 SearXNG +1. 内建工具(get_identity、list_memories 等)使用本地实现 +2. `search_web` 使用 SearXNG 3. Tavily 搜索能力走 MCP 侧 `tavily_search` / `tavily_crawl` / `tavily_research` 4. GitHub、arXiv、PRTS Wiki 等按需作为 MCP 接入 -5. 不为统一形式强行把所有工具都改成 MCP +5. 内置工具、直连 API 工具与 MCP 工具共用工具调用循环 --- -## 3. 不推荐的方式 - -### 3.1 不推荐直接读取 Codex 的 `config.toml` +## 3. 部署权限与传输选择 -原因: +### 3.1 Docker Socket 的取舍 -- 它是 Codex 的宿主配置,不是项目配置 -- 里面的 server 定义、env 处理、项目 trust 逻辑都不属于 QuickQuip -- 它混有与当前项目无关的本机路径和个人开发环境信息 +GHCR 分发镜像和生产模板镜像内置 Docker CLI。容器中的 `docker` transport 还需要显式挂载 `/var/run/docker.sock`;该挂载赋予业务容器控制宿主机 Docker 的权限,适合可信宿主机。 -QuickQuip 应该维护自己的项目配置。当前实现已经把 MCP 配置并入: - -- `config/llm.toml` - -### 3.2 Docker Socket 的取舍 - -如果 QuickQuip 容器内直接执行: - -```bash -docker run -i --rm ... -``` - -那通常意味着: - -- 容器里要装 Docker CLI -- 容器要挂载 `/var/run/docker.sock` - -这会显著放大权限范围。 - -GHCR 分发镜像和生产模板镜像已内置 Docker CLI,以便需要时启用 `docker` transport。真正启用还必须显式挂载 `/var/run/docker.sock`,这会放大权限范围,仅适合开发环境或可信宿主机。 - -生产环境若已有宿主机上的 Streamable HTTP MCP 服务,优先通过现有 HTTPS MCP 网关复用它们,避免在 QuickQuip Compose 内重复运行 MCP sidecar。只有没有可复用的宿主机 HTTP 服务时,才采用纯 sidecar 模式:在部署编排中将 MCP server 作为独立 service 跑在同一网络里,bot 通过 `transport = "sse"` 或 `transport = "http"` 直连。代码中的四种 transport 均已完整实现,部署时无需依赖 Docker socket。 +生产环境已有 HTTPS MCP 网关时,可通过 HTTP 复用宿主机或外部服务。需要独立部署服务时,可在同一 Compose 网络运行 sidecar,Bot 通过其 HTTP 或 SSE 端点连接。 --- -## 4. 推荐架构 +## 4. 运行时架构 ### 4.1 三层结构 -建议未来按三层来接 MCP: +MCP 集成分为三部分: 1. `src/quickquip/llm/service.py`(MCP 生命周期归属 `service_parts/mcp_lifecycle.py`)/ `src/quickquip/llm/tool_registry.py` -2. `src/quickquip/llm/mcp/`(包,v1.8.9 从单文件 `mcp.py` 拆分而来) -3. `config/llm.toml` 内的 `[[mcp.servers]]` 定义 - -MCP client 的连接生命周期(启动、重载、关闭、工具别名重注册)由 `service_parts/mcp_lifecycle.py` 单一持有。 +2. `src/quickquip/llm/mcp/`:协议协商、传输、工具发现、调用与结果归一化 +3. `data/llm_config.db` 内的 `settings.mcp.servers` 定义 -这样可以保持: +配置运行层管理 MCP 资源代际及租约;连接与工具发现由 `service_parts/mcp_lifecycle.py` 装配。无关字段修改保持已有连接,正在执行的任务持有原资源直到结束。 -- 工具调用抽象稳定 -- MCP 只是工具来源的一种 -- 未来也能同时混用直连 API 工具和 MCP 工具 +内置工具、直连 API 工具与 MCP 工具在同一注册表中管理,对模型提供统一的调用接口。 ### 4.2 当前项目配置形式 -当前项目使用 `config/llm.toml` 配置 MCP server: - -```toml -[mcp] -enabled = true - -[[mcp.servers]] -id = "github" -transport = "docker" -image = "ghcr.io/github/github-mcp-server" -env = { GITHUB_PERSONAL_ACCESS_TOKEN = "${GITHUB_PERSONAL_ACCESS_TOKEN}" } -include_tools = ["search_repositories", "search_code", "get_file_contents"] - -[[mcp.servers]] -id = "arxiv" -transport = "docker" -image = "arxiv-mcp-server:latest" -mounts = ["${MCP_ARXIV_PAPERS_MOUNT:-arxiv-papers:/root/.arxiv-mcp-server/papers}"] +当前项目在导出 JSON 的 `settings.mcp.servers` 配置 MCP server,保存草稿后单独应用: + +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "github", + "transport": "docker", + "image": "ghcr.io/github/github-mcp-server", + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" + }, + "include_tools": [ + "search_repositories", + "search_code", + "get_file_contents" + ] + }, + { + "id": "arxiv", + "transport": "docker", + "image": "arxiv-mcp-server:latest", + "mounts": [ + "${MCP_ARXIV_PAPERS_MOUNT:-arxiv-papers:/root/.arxiv-mcp-server/papers}" + ] + } + ] + } + } +} ``` -这份配置只服务于 QuickQuip,不混入 Codex 配置。 +此片段合入完整导出文件时,保留其他设置与需要继续使用的服务。 --- ## 5. 远程部署准备 -如果某个 MCP server 要接入 QuickQuip,远程服务器应提前准备: - -1. 安装 Docker -2. 预拉对应镜像 -3. 预建所需卷 -4. 准备所需 API key / token -5. 明确 QuickQuip 将通过哪种方式访问这些 MCP - -### 5.1 三种接法 +接入前先确认服务提供的端点或启动方式,并准备凭据。`stdio` 需要 Bot 环境中的可执行命令及依赖;`docker` 需要 Docker daemon、镜像和卷;`http`、`sse` 需要 Bot 可访问的服务端点。具体配置见[MCP 教程](mcp-tutorial.md#3-四种-transport-逐一实例)。 -#### A. 内建实现 - -适用: - -- `get_identity` → 本地词表 -- `list_memories` → 本地 SQLite / store - -优点: - -- 最稳 -- 最简单 - -#### B. QuickQuip 自己作为 MCP client,按需启动 server - -适用: - -- GitHub MCP -- arXiv MCP -- PRTS Wiki MCP -- Tavily MCP(搜索、爬取、调研) - -优点: - -- 与现有工具调用框架契合 -- 后续可扩展更多 server - -代价: - -- 要处理进程拉起、超时、stderr、重试 -- `docker` transport 需要宿主机 Docker daemon 与 `docker.sock` - -#### C. 宿主机单独桥接 - -适用: - -- 不希望业务容器直接碰 Docker 权限 - -优点: +--- -- 安全边界更清晰 +## 6. 控制模型工具集合 -代价: +模型使用 MCP 工具需要开启 `settings.runtime.tool_calling_enabled`,默认值为 `false`。MCP 总开关和对应服务开关控制连接,工具调用总开关控制模型调用。 -- 要额外维护一层 bridge / launcher +对于工具较多的服务,先在 `settings.mcp.servers` 中使用 `include_tools` 和 `exclude_tools` 选择需要注册的工具,再通过 `settings.tools` 管理模型可见的集合。`discovery_mode = "auto"` 在可延迟工具数超过 `discovery_min_tools` 时启用发现:首轮发送 `always_loaded` 常驻工具,模型用 `tool_search` 搜索能力,或用 `tool_list` 查看目录、按精确名称加载。新增工具进入下一轮请求。 ---- - -## 6. 对当前项目的明确建议 - -现阶段建议如下: - -- `search_web` - - 继续硬编码走 SearXNG -- `get_identity` - - 继续走本地词表 -- `list_memories` - - 继续走本地 SQLite / store -- Tavily 搜索 - - 走 MCP 侧 `tavily_search` / `tavily_crawl` / `tavily_research` -- GitHub / arXiv / PRTS Wiki - - 已支持作为 MCP 接入 - - 是否启用由 `config/llm.toml` 与环境变量控制 -- 大批量 MCP 工具 - - 在 `[[mcp.servers]]` 上用 `include_tools` / `exclude_tools` 先治理工具集合 - - 通过 `[tools] discovery_mode = "auto"` 走本地 `tool_search` 按需发现 - - `tool_search` 搜不到但工具存在时,可用 `tool_list` 列工具组并按精确名称加载 - - 初始请求只暴露 `always_loaded` 中的常驻工具,匹配到的 MCP 工具会在下一轮工具调用中加载 - -也就是说: - -- 现有工具调用框架先服务项目内部工具 -- MCP 后续作为可插拔扩展层加入 -- MCP 工具数量较多时,先用 MCP server 级过滤控制能力面,再用工具发现控制提示词体积 -- 不要为了 MCP 而重写已经稳定工作的直连能力 +严格工具名单应包含发现元工具和需要使用的 MCP 别名。 工具发现的实现边界与测试覆盖见 [tool-discovery.md](tool-discovery.md)。 --- -## 7. 后续实现建议 - -如果继续扩展 MCP,建议顺序如下: - -1. 补充 `tools/list_changed` 的动态刷新 -2. 为 Docker 型 server 增加更细的状态诊断 -3. 把部分 `env` 从 `config/llm.toml` 进一步抽到更细的部署层 -4. 按需要继续接新的 MCP server +## 7. 扩展与维护 -不要一开始就同时接多个 server。 +新增服务按传输、协商模式、工具清单和结果类型逐项核对。凭据通过根目录 `.env` 提供,配置库保存变量引用;卷挂载由部署编排准备。MCP 扩展计划集中记录在[ROADMAP](../../ROADMAP.md)。 --- @@ -253,25 +146,36 @@ QuickQuip 的 `negotiation` 字段控制每个 HTTP MCP Server 的协商模式 | 模式 | 行为 | |---|---| -| `legacy`(默认) | 只走 `initialize` + session,兼容所有旧 Server。缺省时自动生效,行为与旧版完全一致。 | +| `legacy`(默认) | 使用 `initialize` 握手和服务器提供的 session;适用于 legacy Server。 | | `auto` | 先发 `server/discover` 探测;如果 Server 返回 DiscoverResult 就走 modern;如果返回 legacy 信号(JSON-RPC error、400/404/405 无 modern error body)就回退 legacy。401/403/5xx/超时直接失败,不回退。 | | `modern` | 只走 modern 协议,不回退。 | ### 配置示例 -```toml -[[mcp.servers]] -id = "modern_api" -transport = "http" -negotiation = "auto" -supported_protocol_versions = ["2026-07-28"] -url = "https://modern-mcp.example.com/mcp" +```json +{ + "settings": { + "mcp": { + "servers": [ + { + "id": "modern_api", + "transport": "http", + "negotiation": "auto", + "supported_protocol_versions": [ + "2026-07-28" + ], + "url": "https://modern-mcp.example.com/mcp" + } + ] + } + } +} ``` ### 协商规则 -- `stdio`、`docker`、`sse` transport 只支持 legacy。配置 `auto`/`modern` 会在配置校验阶段被跳过并记录 warning。 -- `supported_protocol_versions` 为空时 `auto`/`modern` 也会被跳过。 +- `stdio`、`docker`、`sse` transport 只支持 legacy;配置 `auto`/`modern` 时保存或导入预览返回字段错误,须修正后再保存草稿。 +- `auto`/`modern` 要求非空 `supported_protocol_versions`;缺失时整个配置校验失败。运行时连接与协商失败通过对应 server 状态报告,配置校验和网络装载分别处理。 - `auto` 探测的结论在当前装载周期内保持:session 过期重连不重新探测,`/llm mcp reload` 或 `/llm reload` 触发的重新装载会重新探测。 - modern version 无交集时明确报 negotiation failure。 - `tools/call` 在 modern 模式下收到 `InputRequiredResult`(MRTR)时返回稳定的 unsupported 结果。 @@ -289,7 +193,3 @@ url = "https://modern-mcp.example.com/mcp" - `_describe_server` 对 HTTP/SSE URL 脱敏(去除 query string 和 fragment)。 - 异常消息中的 URL 和凭据经过清洗后才进入 status JSON 或日志。 - alias 冲突采用 fail-closed:冲突的 binding 全部不注册,status 标记 `failure_kind = "config"`。 - -## 10. 当前文档结论 - -QuickQuip 当前已经可以接 MCP,但实际部署时仍应把它视为项目自己的外部工具后端,并通过项目自己的私有部署环境变量、卷挂载和云端开关来管理。 diff --git a/skills.example/self-docs/references/docs-dev-mcp-tutorial.md b/skills.example/self-docs/references/docs-dev-mcp-tutorial.md index 55c08244..0f40d323 100644 --- a/skills.example/self-docs/references/docs-dev-mcp-tutorial.md +++ b/skills.example/self-docs/references/docs-dev-mcp-tutorial.md @@ -2,11 +2,15 @@ # 从零理解 MCP —— 以 QuickQuip 项目为例 +本文解释 MCP 的概念、传输方式和 QuickQuip 的协议行为。第一次接入服务可先完成[本地 echo 练习](../admin/mcp-servers.md#首次接入本地-echo-练习),再对照本文理解连接与调用过程。 + +配置示例是完整导出 JSON 中的字段片段。使用 Web Admin 时,在“工具与技能 → MCP 服务与状态”添加服务并按示例填写字段。使用 JSON 时,先导出完整配置,将服务对象追加到 `settings.mcp.servers`,保留其他字段与已有服务,再预览导入、保存草稿并应用。完整交换流程见 [LLM 配置管理](../admin/llm-configuration.md)。 + > **面向读者:** 听说过 MCP、尚未实际接触过协议本身的开发者与高级部署者。 > -> **前置要求:** 会读写 TOML 配置;对 QuickQuip 的 LLM 工具调用链路有大致印象(可先浏览 [`llm-module.md`](llm-module.md))。 +> **前置要求:** 会阅读 JSON 配置;对 QuickQuip 的 LLM 工具调用链路有大致印象(可先浏览 [`llm-module.md`](llm-module.md))。 > -> **源码指引:** 协议实现位于 `src/quickquip/llm/mcp/`,配置解析位于 `src/quickquip/llm/config.py`,运行期生命周期位于 `src/quickquip/llm/service_parts/mcp_lifecycle.py`,命令入口位于 `src/quickquip/adapters/nonebot/command_parts/llm.py`。配置权威模板为 `config/llm.toml.example`。 +> **源码指引:** 协议实现位于 `src/quickquip/llm/mcp/`,配置校验与编译位于 `src/quickquip/llm/config_document.py`,运行期生命周期位于 `src/quickquip/llm/service_parts/mcp_lifecycle.py`,命令入口位于 `src/quickquip/adapters/nonebot/command_parts/llm.py`。配置权威模板为 `config/llm.json.example`。 --- @@ -24,15 +28,17 @@ ## 1. MCP 是什么:解决什么问题 -MCP(Model Context Protocol,模型上下文协议)是一套为「AI 应用 × 工具提供方」定义交互方式的开放协议。它要解决的问题是集成成本的 N×M 困境:工具提供方(搜索引擎、代码托管平台、数据服务……)各自暴露一套私有接口,AI 应用方每接一个新工具都要写一份专门的对接代码——M 个工具 × N 个应用就是 M×N 份胶水,任何一侧变动都牵动另一侧。 +假设你问机器人:“某个仓库的 README 写了什么?”模型可以提出读取文件的工具请求,QuickQuip 把请求交给仓库服务,服务读取文件并返回内容,模型再据此组织回复。模型负责判断需要什么信息,QuickQuip 负责联系工具服务,服务负责执行具体操作。 + +MCP(Model Context Protocol,模型上下文协议)规定应用与工具服务如何介绍能力、发送请求和返回结果。工具提供方(搜索引擎、代码托管平台、数据服务……)各自暴露一套私有接口时,M 个工具与 N 个应用需要 M×N 份专门的对接代码;MCP 让双方复用一套协议实现。 -MCP 把这层关系标准化:工具提供方实现一次 **MCP server**,把能力以 **tools**(可调用工具)和 **resources**(只读资源)的形式声明出来;AI 应用方实现一次 **MCP client**,按协议完成发现、协商与调用。此后每新增一个 server,所有 client 直接获得它的工具;每新增一个 client,天然能用上全部存量 server。 +MCP 把这层关系标准化:工具提供方实现 **MCP server**,把能力以 **tools**(可调用工具)和 **resources**(只读资源)的形式声明出来;AI 应用方实现 **MCP client**,按协议完成发现、协商与调用。接入新的服务时,应用复用这套协议实现,再配置兼容的传输、版本、凭据与工具范围。 ### 1.1 四个核心概念 | 概念 | 含义 | 在 QuickQuip 中的落点 | |------|------|----------------------| -| client | 协议中的调用方端点,负责与单个 server 通信 | `MCPClient`(每个 server 一个实例,`mcp/client.py`) | +| client | 向工具服务发送请求并接收结果的调用方 | `MCPClient`(每个 server 一个实例,`mcp/client.py`) | | server | 工具提供方,声明并执行工具 | GitHub MCP、arXiv MCP、Tavily MCP 等 | | tools | server 暴露的可调用能力(名称 + 描述 + 参数 schema) | 桥接进 `ToolRegistry` 的 `mcp_*` 工具 | | resources | server 暴露的只读数据 | QuickQuip 只消费工具结果中内联的 resource 文本(见 §5.4) | @@ -61,7 +67,7 @@ QuickQuip(client) MCP Server 3. **调用**:模型决定使用某工具后,host 通过 client 发出 `tools/call`,携带工具名与参数; 4. **结果**:server 返回 content 列表,client 做安全归一化(§5.4)后交回工具调用循环。 -QuickQuip 的协议面只落在这三个核心方法上:`initialize`(legacy 握手)、`tools/list`、`tools/call`,外加一个 `notifications/initialized` 通知(`mcp/client.py`)。这套协议面之下由 transport 承载消息、之上桥接进项目的工具注册表,正是下一节的主题。 +QuickQuip 用 `initialize` 与 `notifications/initialized` 完成 legacy 握手,用 `server/discover` 发现 modern 服务,再通过 `tools/list` 和 `tools/call` 发现和调用工具。transport 承载这些消息,项目工具注册表将发现的能力提供给模型。 --- @@ -71,9 +77,9 @@ QuickQuip 把 MCP 作为工具后端来源之一:MCP 工具与内置工具进 ### 2.1 启动时发生了什么 -`MCPClientManager.sync()`(`mcp/client.py`)在启动或重载时逐个处理 `[[mcp.servers]]`: +`MCPClientManager.sync()`(`mcp/client.py`)在启动或重载时逐个处理 `settings.mcp.servers`: -1. 建立连接:每个 server 最多尝试 3 次,间隔 2 秒。认证失败(401/403)、配置类错误与 4xx 直接判死不重试;超时、网络错误与 5xx 视为瞬态(应对 compose 冷启动时 sidecar 尚未就绪的竞态); +1. 建立连接:每个 server 最多尝试 3 次,间隔 2 秒。认证失败(401/403)、配置类错误与 4xx 直接报告失败;超时、网络错误与 5xx 按瞬态错误重试,可处理 Compose 冷启动时服务尚未就绪的情况; 2. `tools/list` 拉取工具清单(分页循环),生成工具别名并按 server 级名单过滤(§4.2); 3. 桥接注册:`mcp_lifecycle.py` 把每个工具按别名注册进 `ToolRegistry`,来源与分类标记为 `mcp:`,描述冠以 `[MCP/]` 前缀; 4. 写出状态文件(`data/mcp_status.json`),供 Web Admin 展示同一份装载结果。 @@ -85,12 +91,12 @@ QuickQuip 把 MCP 作为工具后端来源之一:MCP 工具与内置工具进 | 层 | 配置 | 生效时机 | |----|------|---------| | server 级 | `include_tools` / `exclude_tools` | 桥接前,决定哪些 MCP 工具被注册 | -| 全局级 | `[tools] enabled` + `enabled_mode` | 组装请求时,决定暴露给模型的工具集合 | -| 会话级 | `[tools] discovery_mode` | 决定首轮携带哪些工具、其余如何按需加载 | +| 全局级 | `settings.tools.enabled` + `enabled_mode` | 组装请求时,决定暴露给模型的工具集合 | +| 请求加载策略 | `settings.tools.discovery_mode` | 全局配置决定首轮携带哪些工具,单次工具循环按需加载其他工具 | -全局级的行为:`enabled = []` 时暴露默认白名单加全部 MCP 工具;`enabled_mode = "append"` 在此之上追加所列工具;`enabled_mode = "replace"` 精确过滤,只暴露名单内的工具(MCP 工具也会被名单滤掉)。 +模型使用工具前,需开启 `settings.runtime.tool_calling_enabled`,其默认值为 `false`。全局级的行为是:`enabled = []` 时使用默认名单加全部已注册 MCP 工具;非空名单中,`enabled_mode = "append"` 在默认集合上追加所列工具,`enabled_mode = "replace"` 按所列名称精确过滤,MCP 工具同样受该名单约束。空名单在两种模式下都使用默认集合。 -会话级与工具发现:`discovery_mode = "auto"`(默认)时,首轮请求只携带 `always_loaded` 常驻工具,模型用本地元工具 `tool_search` 按需搜索、`tool_list` 列目录或按精确名称加载,命中的 MCP 工具在下一轮请求中生效。接入大批量 MCP 工具时,先用 server 级 `include_tools` 收窄能力面,再交给发现机制控制提示词体积;实现细节见 [`tool-discovery.md`](tool-discovery.md)。 +`discovery_mode = "auto"`(默认)在可延迟工具数超过 `discovery_min_tools` 时开启发现机制:首轮请求携带 `always_loaded` 常驻工具,模型用 `tool_search` 搜索,或用 `tool_list` 查看目录、按精确名称加载。新加载的 MCP 工具在下一轮请求中生效;小工具集直接发送全部启用工具。接入大批量 MCP 工具时,先用 server 级 `include_tools` 选择能力,再通过发现机制控制工具定义体积。实现细节见[工具发现说明](tool-discovery.md)。 ### 2.3 查看装载结果:`/llm mcp status` @@ -103,30 +109,39 @@ MCP 状态 - github [docker] ERROR tools=0 error=认证失败 ``` -- 聊天面只显示失败分类(如 `认证失败`),不显示服务端原始错误文本;Web Admin 的状态页可看清洗后的详情; +- 聊天面显示失败分类,如 `认证失败`;Web Admin 的状态页提供清洗后的错误详情; - transport 后面的 `/modern`、`/auto/legacy` 等角标是双协议纪元标记(§5.1); - `/llm mcp reload`(管理员):重连全部 server,docker transport 会先强制拉取最新镜像; -- `/llm reload`(管理员):重载 `llm.toml` 并在后台重连 MCP;人格热重载路径不会触碰 MCP 连接。 +- LLM 配置通过草稿应用任务接纳;仅 MCP 配置改变时准备新资源代际。人格热重载与价格更新保持当前 MCP 连接。 --- ## 3. 四种 transport 逐一实例 -`transport` 决定 client 与 server 之间消息怎么传输。四种都封装在 `mcp/transport.py`,协议层完全无感。以下配置块均可直接复制进 `config/llm.toml` 后按需改名。 +`transport` 决定 client 与 server 之间消息怎么传输。四种都封装在 `mcp/transport.py`,共用协议层。以下示例用于对照传输方式与字段;服务准备、表单填写和实际调用步骤见[管理员接入指南](../admin/mcp-servers.md)。 ### 3.1 stdio —— 本地子进程 适用场景:与 bot 同机的命令行 MCP server(`uvx` 拉起的 Python server、`npx` 拉起的 Node server 等),进程随 bot 启停。 -```toml -[mcp] -enabled = true - -[[mcp.servers]] -id = "fetch" -transport = "stdio" -command = "uvx" -args = ["mcp-server-fetch"] +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "fetch", + "transport": "stdio", + "command": "uvx", + "args": [ + "mcp-server-fetch" + ] + } + ] + } + } +} ``` 机制:QuickQuip 派生子进程,stdin/stdout 上交换 JSON-RPC(按行分隔,自动兼容 `Content-Length` 帧格式),stderr 逐行写入日志;`env` 在当前进程环境之上合并注入。 @@ -137,17 +152,30 @@ args = ["mcp-server-fetch"] 适用场景:社区只提供 CLI 镜像、没有 http/sse 端点的 server。需要本机 Docker CLI 与 daemon;容器化部署默认不挂载 docker.sock,仅适合裸机或可信宿主机。 -```toml -[mcp] -enabled = true - -[[mcp.servers]] -id = "github" -transport = "docker" -timeout_seconds = 30 -image = "ghcr.io/github/github-mcp-server" -env = { GITHUB_PERSONAL_ACCESS_TOKEN = "${GITHUB_PERSONAL_ACCESS_TOKEN}" } -include_tools = ["search_repositories", "get_file_contents", "search_code"] +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "github", + "transport": "docker", + "timeout_seconds": 30, + "image": "ghcr.io/github/github-mcp-server", + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" + }, + "include_tools": [ + "search_repositories", + "get_file_contents", + "search_code" + ] + } + ] + } + } +} ``` 机制:QuickQuip 执行 `docker run -i --rm --pull ...` 拉起容器;`env` 写入一块 0600 权限的临时 `--env-file` 传给容器,启动完成后立即删除,凭证不出现在进程命令行里。 @@ -158,44 +186,67 @@ include_tools = ["search_repositories", "get_file_contents", "search_code"] 适用场景:自建或第三方的 HTTPS MCP 服务、宿主机 MCP 网关。生产环境首选。 -```toml -[mcp] -enabled = true - -[[mcp.servers]] -id = "prts_wiki" -transport = "http" -timeout_seconds = 30 -url = "https://mcp.example.com/mcp" -headers = { Authorization = "Bearer ${MCP_PRTS_WIKI_TOKEN}" } +端点是服务提供方给 MCP 客户端使用的完整 URL,通常含 `/mcp`;按该服务的说明填写。下面的 `mcp.example.com` 是占位地址,`MCP_PRTS_WIKI_TOKEN` 代表该服务签发的凭据。服务地址和凭据取得后替换示例;可直接启动的练习服务见[本地 echo 练习](../admin/mcp-servers.md#首次接入本地-echo-练习)。 + +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "prts_wiki", + "transport": "http", + "timeout_seconds": 30, + "url": "https://mcp.example.com/mcp", + "headers": { + "Authorization": "Bearer ${MCP_PRTS_WIKI_TOKEN}" + } + } + ] + } + } +} ``` -机制:单端点 POST;服务器从响应头下发 `mcp-session-id`,后续请求携带该头维持会话;响应体是 JSON 或内联 SSE。 +机制:请求发往同一 POST 端点,响应为 JSON 或内联 SSE。默认 legacy 模式下,服务器可从响应头下发 `mcp-session-id`,后续请求携带该头;modern 模式按 §5.1 的逐请求元数据通信。 验证:可先用 curl 模拟一次 legacy 握手确认端点与凭证可达: ```bash -curl -sS -X POST "https://mcp.example.com/mcp" \ +curl -i -sS --max-time 10 -X POST "https://mcp.example.com/mcp" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer ${MCP_PRTS_WIKI_TOKEN}" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' ``` -返回 JSON-RPC result 即端点可用;再以 `/llm mcp status` 确认装载。 +在 Bash 中执行该命令,先设置令牌环境变量,并将示例 URL 换成实际端点。Streamable HTTP 请求须通过 `Accept` 声明同时接收 JSON 与 SSE,[协议要求见此](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports)。`-i` 显示 HTTP 状态和响应头,`--max-time 10` 在 10 秒后结束探测。 -### 3.4 sse —— 经典 HTTP+SSE - -适用场景:仍只提供旧式 SSE 端点的存量远程 server。 +正常响应为 HTTP 200:`Content-Type: application/json` 时,正文包含初始化 `result`;`text/event-stream` 时,事件的 `data:` 中包含同一 JSON-RPC 结果。部分 legacy 服务还返回 `Mcp-Session-Id` 响应头。401/403 时核对凭据,404/405 时核对 MCP 端点与传输方式;随后通过 `/llm mcp status` 查看 Bot 装载状态,按接入指南发起一次工具调用。探测本地 echo 时,URL 使用 `http://127.0.0.1:8017/mcp`,省略 `Authorization` 头。 -```toml -[mcp] -enabled = true +### 3.4 sse —— 经典 HTTP+SSE -[[mcp.servers]] -id = "tavily" -transport = "sse" -timeout_seconds = 30 -url = "http://mcp-tavily:8080/sse" +适用场景:提供经典 SSE 端点的远程 server。 + +下例假定你已在 Bot 所在网络启动名为 `mcp-tavily` 的服务,且该服务提供 8080 端口的 `/sse` 入口;容器名、端口和路径按实际服务调整。 + +```json +{ + "settings": { + "mcp": { + "enabled": true, + "servers": [ + { + "id": "tavily", + "transport": "sse", + "timeout_seconds": 30, + "url": "http://mcp-tavily:8080/sse" + } + ] + } + } +} ``` 机制:GET 打开一条长连接事件流,服务器发出 `endpoint` 事件告知 POST 地址(相对路径会按 SSE URL 解析),请求的响应从 `message` 事件回流。等待 `endpoint` 事件超过 `timeout_seconds` 会报「等待 endpoint 事件超时」。 @@ -206,17 +257,17 @@ url = "http://mcp-tavily:8080/sse" ## 4. 配置全解 -配置全部写在 `config/llm.toml`,解析代码为 `src/quickquip/llm/config.py` 的 `_read_mcp_servers()`。总开关 `[mcp] enabled` 默认 `false`;关闭时 `sync()` 直接返回,一个连接也不建立。 +配置通过共享应用服务保存到 `data/llm_config.db`,校验与编译见 `src/quickquip/llm/config_document.py`。总开关 `settings.mcp.enabled` 默认 `false`;关闭时 `sync()` 直接返回,一个连接也不建立。 -### 4.1 `[[mcp.servers]]` 全字段表 +### 4.1 `settings.mcp.servers` 全字段表 -默认值以现行解析代码为准(`MCPServerConfig` 与 `_read_mcp_servers`)。 +默认值以 `MCPServerConfig` 为准;JSON 写入约束由 `config_document.py` 校验。保存与导入预览返回具体字段错误,修正后才能保存整份草稿。 **通用字段** | 字段 | 默认值 | 说明 | |------|--------|------| -| `id` | (必填) | server 唯一标识;缺失或与已出现 id 重复的条目整段跳过(重复时记录告警),也是工具别名与 `mcp:` 分类的来源 | +| `id` | (必填) | server 唯一标识;缺失或重复会使配置校验失败,也是工具别名与 `mcp:` 分类的来源 | | `transport` | `"stdio"` | `stdio` / `docker` / `http` / `sse` | | `enabled` | `true` | 单 server 开关;`false` 时状态记为 disabled,不参与连接 | | `timeout_seconds` | `30` | 连接、探测与每次请求等待的上限秒数(float) | @@ -266,7 +317,7 @@ docker 的 `args` 附加在镜像名之后,作为 server 自身参数;`env` |------|--------|------| | `protocol_version` | `"2025-03-26"` | legacy 握手时声明的协议版本 pin | | `negotiation` | `"legacy"` | `legacy` / `auto` / `modern`,仅 `http` transport 生效(§5.2) | -| `supported_protocol_versions` | `[]` | `auto` / `modern` 模式下客户端声明的可接受版本列表(这两种模式必填非空,否则该 server 在配置校验阶段被跳过并告警) | +| `supported_protocol_versions` | `[]` | `auto` / `modern` 模式下客户端声明的可接受版本列表;这两种模式必填非空,缺失使配置校验失败 | ### 4.2 `${ENV_VAR}` 展开规则与时机 @@ -278,14 +329,14 @@ ${ENV_VAR:-default} 带默认值;未设置时展开为 default(默认值 ``` - 变量名需匹配 `[A-Za-z_][A-Za-z0-9_]*`;占位符支持嵌在更长字符串里(如 `"Bearer ${TOKEN}"`); -- 展开发生在**配置解析时**:进程启动加载 `llm.toml` 与 `/llm reload` 重载时各展开一次,运行期不重读环境变量; -- `.env` 只在进程启动时加载一次:`/llm reload` 重载的是 TOML 展开(环境变量取自启动时快照),改动 `.env` 需要重启 bot 进程生效。 +- 展开发生在**配置解析时**:Bot 编译已发布快照或应用指定快照时展开,运行期不重读环境变量; +- `.env` 只在进程启动时加载一次:快照编译从 Bot 当前进程环境读取变量,改动 `.env` 需要重启 bot 进程生效。 ### 4.3 凭证安全惯例 -- 凭证一律放进程环境(部署上以仓库根目录 `.env` 为唯一涉密来源),`llm.toml` 里只写 `${ENV_VAR}` 占位,任何真实 token 不进配置文件; +- 凭证一律放进程环境(部署上以仓库根目录 `.env` 为唯一涉密来源),JSON 和配置数据库里只写 `${ENV_VAR}` 占位,任何真实 token 不进配置文件; - http/sse 用 `headers` 注入 `Authorization`,stdio 用 `env`,docker 的 `env` 经临时 `--env-file` 注入,凭证不暴露在进程命令行; -- 状态与日志侧有配套清洗:URL 去除 query、fragment 与 userinfo,异常文本截断脱敏后才能进入 status JSON 或日志——即使 token 误写进 URL,也不会从状态页泄漏。 +- 状态与日志对 URL 去除 query、fragment 和 userinfo,对异常文本做截断和凭据清洗。使用环境变量引用配置凭据,避免把令牌写进 URL。 --- @@ -311,18 +362,29 @@ MCP 规范自 `2026-07-28` 版起划分为两个纪元,QuickQuip 两者都支 | `auto` | 先发 `server/discover` 探测:返回 DiscoverResult 就走 modern;收到 legacy 信号(JSON-RPC error,或 400/404/405 且响应体无 modern 错误码 -32022/-32020)就回退 legacy。401/403/5xx/超时直接失败不回退 | | `modern` | 只走 modern;探测判定为 legacy 时报协议协商失败 | -- `negotiation` 配了 `auto`/`modern` 但 `transport` 非 `http`,或 `supported_protocol_versions` 为空:该 server 在配置校验阶段被跳过并记录告警(不会到连接阶段才失败); +- `negotiation` 配了 `auto`/`modern` 但 `transport` 非 `http`,或 `supported_protocol_versions` 为空:保存校验返回字段错误,修复后才可保存并应用; - modern 版本协商取客户端声明列表与服务器 `supportedVersions` 的交集,交集为空时报「modern 版本无交集」; - modern 模式下收到 `InputRequiredResult`(MRTR,响应带 `inputRequests`)按「暂不支持」返回稳定错误; -- 协商在每次装载时执行(进程启动、`/llm reload`、`/llm mcp reload` 都会重新探测);session 过期重连沿用当前装载周期的协商结论,不重新探测。 - -```toml -[[mcp.servers]] -id = "modern_api" -transport = "http" -negotiation = "auto" -supported_protocol_versions = ["2026-07-28"] -url = "https://modern-mcp.example.com/mcp" +- 协商在连接资源准备时执行(进程启动、MCP 配置应用、`/llm mcp reload`);session 过期重连沿用当前装载周期的协商结论,不重新探测。 + +```json +{ + "settings": { + "mcp": { + "servers": [ + { + "id": "modern_api", + "transport": "http", + "negotiation": "auto", + "supported_protocol_versions": [ + "2026-07-28" + ], + "url": "https://modern-mcp.example.com/mcp" + } + ] + } + } +} ``` ### 5.3 stale session 处理(legacy HTTP) @@ -332,7 +394,7 @@ url = "https://modern-mcp.example.com/mcp" - `tools/list` 等只读请求:有界重连,最多 2 次——重新 `initialize` 换取新 session-id;新连接不继承旧 session-id 与旧 request-id; - `tools/call`:**不自动重放**,直接失败并报「session 过期,未自动重放」。工具调用可能有副作用,重放会造成重复执行。 -对使用者的含义:偶发的 404 对工具清单无感知;聊天中偶见「MCP 工具 xxx 调用失败:…session 过期」时,下一轮对话通常已用新会话自动恢复,持续出现才需要排查服务器侧会话超时设置。 +工具清单遇到会话过期时在上述次数内重连;工具调用返回失败并标记需要重连,后续请求建立新会话。反复出现会话过期时,检查服务器会话保留时间和网络状态。 ### 5.4 工具结果内容边界 @@ -362,9 +424,9 @@ url = "https://modern-mcp.example.com/mcp" | 根因 | 表现 | |------|------| -| `url` / `command` / `image` 缺失 | 配置错误,日志明示「缺少 url/command/image」 | -| `id` 重复 | 后出现的条目被跳过并告警 | -| `negotiation = "auto"/"modern"` 但 transport 非 http,或未填 `supported_protocol_versions` | 配置校验阶段跳过并告警,status 里根本不出现 | +| `url` / `command` / `image` 缺失 | 对应 transport 装载时返回配置错误,日志明示缺失字段;补齐连接配置后保存并应用 | +| `id` 重复 | 整份配置校验失败,调整为唯一 ID 后再保存 | +| `negotiation = "auto"/"modern"` 但 transport 非 http,或未填 `supported_protocol_versions` | 配置校验失败;http 的新纪元模式需声明非空版本列表 | | 两个 server 的工具归一后同名 | fail-closed,冲突绑定全部不注册,error 为「alias 冲突」 | | compose 冷启动竞态 | 超时/5xx 自动重试 3 次,通常自愈;认证/4xx 不重试 | | 凭证无效 | 认证失败,检查 `.env` 与 `headers`/`env` 占位是否展开(展开为空串时服务器侧表现为匿名请求) | @@ -377,7 +439,7 @@ url = "https://modern-mcp.example.com/mcp" **处置**: -1. 慢工具(深度调研、大范围爬取类)给所在 server 单独调大 `timeout_seconds`,`/llm reload` 生效; +1. 慢工具(深度调研、大范围爬取类)给所在 server 单独调大 `timeout_seconds`,保存并应用后生效; 2. docker transport 首次调用前可能需要拉镜像:检查 `pull_policy`,用 `/llm mcp reload` 强制拉取; 3. 间歇性超时优先排查 server 端负载与网络;持续超时且 `tools/list` 正常,多半是单个工具执行确实超过阈值。 @@ -385,7 +447,7 @@ url = "https://modern-mcp.example.com/mcp" **现象**:工具文本末尾出现 `…[MCP resource 正文超长,已截断]`,或提示 `MCP 工具省略了 N 个无效或超出限制的图片项` / `MCP 工具省略了尚未支持的内容:N 个 resource 项`。 -**解释**:这些提示全部来自 §5.4 的结果边界,属于刻意防护——正文仍会正常进入模型,只是超界部分被收拢为固定提示。 +**解释**:结果处理按 §5.4 的规则交付可用文本和有效图片,对截断、无效或暂不支持的内容附加省略提示。 **处置**: @@ -397,16 +459,10 @@ url = "https://modern-mcp.example.com/mcp" ## 7. 延伸阅读 -- [`docs/admin/mcp-servers.md`](../admin/mcp-servers.md):接入实操清单,部署侧视角的 server 逐家配置与验证步骤; +- [`docs/admin/mcp-servers.md`](../admin/mcp-servers.md):本地 echo 首次练习、服务配置与工具调用验证; - [`mcp-integration.md`](mcp-integration.md):设计决策与边界——目标边界、推荐路线、docker socket 取舍与安全决策的记录; - [`tool-discovery.md`](tool-discovery.md):工具发现的策略、数据流与测试覆盖; -- [`docs/admin/configuration.md`](../admin/configuration.md):`config/llm.toml` 全量字段参考(含 `[mcp]` 段); +- [`docs/admin/configuration.md`](../admin/configuration.md):`data/llm_config.db` 全量字段参考(含 `settings.mcp` 段); - MCP 上游规范:——协议方法、传输定义与各版本规范的权威来源。 --- - -> **文档信息** -> -> - 本文档基于 QuickQuip 项目编写,全部字段、默认值与行为描述以现行实现为准 -> - 相关源码:`src/quickquip/llm/mcp/`、`src/quickquip/llm/config.py`、`src/quickquip/llm/service_parts/mcp_lifecycle.py`、`src/quickquip/adapters/nonebot/command_parts/llm.py` -> - 最后更新:2026-09-19 diff --git a/skills.example/self-docs/references/docs-dev-readme.md b/skills.example/self-docs/references/docs-dev-readme.md index ae21ce1a..2f8e4008 100644 --- a/skills.example/self-docs/references/docs-dev-readme.md +++ b/skills.example/self-docs/references/docs-dev-readme.md @@ -9,7 +9,7 @@ | 路径 | 是否公开 | 权威性 | 内容 | |---|---|---|---| | `docs/dev/` | 追踪并公开 | 当前开发与运行时契约 | 架构、工程规范、实现说明、测试与发布流程 | -| 本地 gitignore 工作区 | 不公开 | 仅作辅助上下文 | 计划、草稿、私有记录、沙箱、执行证据和历史归档 | +| 本地已忽略工作区 | 不公开 | 辅助上下文 | 计划、草稿、私有记录、沙箱、执行证据和历史归档 | 公开开发文档不得依赖本地工作区中的任何文件。私有计划形成长期规则或已发布行为时,应提炼为本目录或相应的用户/管理员文档,并移除本机路径、私有拓扑、账号、token 和其他敏感内容。 @@ -17,14 +17,19 @@ | 文档 | 职责 | |---|---| +| [`documenting.md`](documenting.md) | 全仓公开文档规范:读者与职责、事实与证据、文风、操作示例、格式、维护与审查 | | [`architecture.md`](architecture.md) | 目录结构、分层、依赖方向、组合根和数据/部署边界 | | [`style.md`](style.md) | 源码结构、可维护性、类型与输入边界、错误与状态、测试和评审问题 | | [`testing.md`](testing.md) | 测试纪律、准入与断言依据、典型反模式、合并删除和验证 | | [`branching.md`](branching.md) | 分支模型、变更分级、验证、评审(含 KHPilot Bot Review 机制与双轨交叉核对)、发布和 hotfix 流程 | | [`versioning.md`](versioning.md) | 主题更新、累积更新、兼容性说明、开发版本与发布候选编号 | | [`record-identities.md`](record-identities.md) | 记录正文、共享身份、引用索引与兼容读取契约 | +| [`llm-configuration-api.md`](llm-configuration-api.md) | LLM 配置修订、结构化交换、参数 metadata 与控制任务的 API 契约 | +| [`llm-configuration-frontend.md`](llm-configuration-frontend.md) | LLM 配置前端交付契约、用户操作、字段语义、运行状态与验收场景 | | [`llm-module.md`](llm-module.md) | LLM 触发、上下文、记忆、provider、配置和运行时边界 | | [`mcp-integration.md`](mcp-integration.md) | MCP 接入、协议协商、工具结果和安全边界 | +| [`mcp-tutorial.md`](mcp-tutorial.md) | MCP 概念、四种 transport 与首次接入练习导航 | +| [`skill-tutorial.md`](skill-tutorial.md) | Skill 安装、无脚本编写与完整脚本实战 | | [`tool-discovery.md`](tool-discovery.md) | LLM 工具发现的策略、模式、限制和测试 | | [`game-framework.md`](game-framework.md) | 游戏注册、经济系统和扩展框架 | | [`regex-tutorial.md`](regex-tutorial.md) | 零基础正则教学(以项目规则为实例)与现行规则体系速览 | @@ -34,10 +39,4 @@ ## 维护规则 -- 新规则写入拥有该决策的文档;只有形成独立、长期的契约时才新建文件。 -- 架构边界、协议行为、配置语义或持久化契约变更时,在同一变更中更新对应文档。 -- 公共文档使用仓库相对链接,不出现私有工作区路径、真实 `prod/` 内容、本机绝对路径、凭据或未经验证的平台结论。 -- Markdown 段落和列表项保持自然换行;仅在 Markdown 结构或语义需要时手动换行。 -- 中文散文使用弯引号(“” ‘’);行内 code 里的命令示例保持 ASCII 直引号(`--preset` 等参数解析器只认直引号)。 -- 交付前按变化范围搜索过时术语、配置键、命令和路径,并如实记录无法执行的验证。 -- 修改 self-docs 同步源内的公开文档(`docs/` 各页面、根目录公开 Markdown、AI 协作配置与 GitHub 模板等;权威名单见 `scripts/ci/sync_self_docs_references.py`)时,在同一变更中运行 `python scripts/ci/sync_self_docs_references.py` 并提交重新生成的 `skills.example/self-docs/references/`(预置 self-docs Skill 随仓库分发的文档副本);CI 契约测试会强制这一同步,未提交的变更会被判红。 +撰写、维护和审查本目录及全仓公开文档时,遵循 [`documenting.md`](documenting.md)。文档职责、表达方式、格式、示例验证、公开信息保护与 self-docs 同步要求统一由该规范维护。 diff --git a/skills.example/self-docs/references/docs-dev-record-identities.md b/skills.example/self-docs/references/docs-dev-record-identities.md index 616f267e..9ce200db 100644 --- a/skills.example/self-docs/references/docs-dev-record-identities.md +++ b/skills.example/self-docs/references/docs-dev-record-identities.md @@ -2,7 +2,7 @@ # 记录正文与成员身份契约 -记忆、语录和留言以 QQ 作为稳定关联键,展示名称按“本群标准身份 → 本群已知名片 → 消息段名称或记录快照 → QQ 占位”解析。群级身份覆盖同 QQ 的全局条目;同名的不同 QQ 保留为多个候选。私聊记录使用既有 `private:` 作用域,仅使用全局身份。 +本文面向记录存储与身份解析的开发者,说明正文格式、关联索引、编辑和检索契约。记忆、语录和留言以 QQ 作为稳定关联键,展示名称按“本群标准身份 → 本群已知名片 → 消息段名称或记录快照 → QQ 占位”解析。群级身份覆盖同 QQ 的全局条目;同名的不同 QQ 保留为多个候选。私聊记录使用既有 `private:` 作用域,仅使用全局身份。 ## 分层与缓存 @@ -23,15 +23,15 @@ Bot 与 Web 使用各自的进程缓存。每份文件至多每 5 秒检查一 | `all` | 无 | 全体成员提及 | | `media` | `media` | `image`、`record`、`video`、`face`、`forward`、`node` 的可读占位 | -结构化 OneBot 消息逐段转换,命令前缀只从命令文字段剥离。文本段中的 CQ 示例保持文字。引用消息提供字符串时,在适配层解析完整 CQ 码及参数转义。记录正文保留机器人和全体成员提及;纯媒体消息继续无法收藏为语录。 +结构化 OneBot 消息逐段转换,命令前缀只从命令文字段剥离。文本段中的 CQ 示例保持文字。引用消息提供字符串时,在适配层解析完整 CQ 码及参数转义。记录正文保留机器人和全体成员提及;语录收藏要求消息包含可保存的正文,纯媒体消息无法收藏。 -`memories`、`quotes`、`offline_messages` 各新增可空 `content_parts_json`,并各自维护 `_member_refs(group_id, record_id, qq)`。正文、片段与引用索引在同一事务中写入;删除触发器同步清理引用索引。记忆归属 `user_id` 与正文提及分别维护,手动 `/remember` 保持群级记忆。 +`memories`、`quotes`、`offline_messages` 各新增可空 `content_parts_json`,并各自维护成员引用表 `
_member_refs(group_id, record_id, qq)`。正文、片段与引用索引在同一事务中写入;删除触发器同步清理引用索引。记忆归属 `user_id` 与正文提及分别维护,手动 `/remember` 保持群级记忆。 -启动只迁移结构。缺少片段的历史正文在读取时兼容解析合法 CQ 提及及有明确边界的 `@QQ数字`;普通数字、名字和已失去 QQ 的 `@名字` 保持原文。历史来源无法区分 CQ 示例和序列化消息,兼容展示结果可通过原文查看核对。 +启动时迁移数据库结构。缺少片段的历史正文在读取时兼容解析合法 CQ 提及及有明确边界的 `@QQ数字`;普通数字、名字和已失去 QQ 的 `@名字` 保持原文。历史来源无法区分 CQ 示例和序列化消息,兼容展示结果可通过原文查看核对。 ## API 与编辑 -原有路由和 `content` 字段保留,新增 `content_display`、`content_parts`、`user_display`,语录保留 `sender_display`。`content` 为写入时的兼容正文,`content_display` 使用当前身份;成员片段中的 `name` 为快照,读取附加的 `display` 为当前名称。 +记录接口返回 `content`、`content_display`、`content_parts` 和 `user_display`,语录还返回 `sender_display`。`content` 为写入时的兼容正文,`content_display` 使用当前身份;成员片段中的 `name` 为快照,读取附加的 `display` 为当前名称。 记忆创建与更新可传 `content_parts`。服务端校验版本、类型、QQ 和长度,再生成兼容正文。仅传 `content` 时作为纯文本保存;仅修改标签或置信度时保留片段。正文变化时重新维护引用索引。后台以文字输入和可删除、可替换的成员块编辑,并提供原文查看。 @@ -41,10 +41,10 @@ Bot 与 Web 使用各自的进程缓存。每份文件至多每 5 秒检查一 记录匹配保留正文关键词能力,并增加明确成员名字、别名、QQ、结构化艾特的关联匹配。匹配、去重在分页和数量限制之前完成;语录作者查询与正文提及查询分别处理。历史兼容解析与显式回填采用同一正文规则。查询侧成员候选在请求内计算一次,群聊搜索和语录正文扫描在工作线程执行;语录长读取使用独立 SQLite 连接。 -Chat 自动检索和记忆工具限定在群记忆及当前用户个人记忆内。人物志先按目标 QQ 选择个人记忆,再应用置信度排序和数量上限。归属标签与事实正文分别传给模型。自动记忆继续使用既有事实字符串输出协议和归属约束,模型自行写出的人名保持纯文本。 +Chat 自动检索和记忆工具限定在群记忆及当前用户个人记忆内。人物志先按目标 QQ 选择个人记忆,再应用置信度排序和数量上限。归属标签与事实正文分别传给模型。自动记忆使用事实字符串输出协议和归属约束,模型自行写出的人名保持纯文本。 `/forget` 复用记忆列表匹配规则;`/forget #编号` 精确删除本群记录。成员名字指向多个身份条目时要求提供 QQ 或编号,保留记录;同一条身份绑定的多个 QQ 作为同一个人匹配。明确输入 QQ 时只关联该号码。 -历史内容发送为显式 OneBot 文本段;通知发送方单独构造真正的 at 段。原始聊天归档与 Chat 历史冻结内容保持既有契约。游戏、经济账户和榜单不属于本正文模型。 +历史内容发送为显式 OneBot 文本段;通知发送方单独构造真正的 at 段。原始聊天归档与 Chat 历史按各自契约保存和冻结内容。游戏、经济账户和榜单使用各自的数据模型。 显式迁移与恢复流程见 [管理员迁移说明](../admin/record-identities.md)。 diff --git a/skills.example/self-docs/references/docs-dev-regex-tutorial.md b/skills.example/self-docs/references/docs-dev-regex-tutorial.md index c310eac8..5a83ed71 100644 --- a/skills.example/self-docs/references/docs-dev-regex-tutorial.md +++ b/skills.example/self-docs/references/docs-dev-regex-tutorial.md @@ -2,11 +2,37 @@ # 从零开始学习正则表达式 —— 以 QuickQuip 项目为例 -> **面向读者:** 零基础的 Python 初学者,希望通过真实项目案例理解正则表达式。 -> -> **前置要求:** 了解基本的 Python 语法(字符串、函数调用)。 -> -> **源码指引:** 本文引用的源码路径以 `src/quickquip/` 下的主实现为准。`src/plugins/` 目录是 NoneBot2 插件入口层,只做 re-export,不包含业务逻辑。文字规则配置在 `config/chat_rules.toml`(部署级私有,权威模板为 `config/chat_rules.toml.example`,由 `src/quickquip/chat/config.py` 加载),规则匹配引擎位于 `src/quickquip/chat/text_rules.py`。 +本教程面向了解 Python 字符串和函数调用的初学者,通过 QuickQuip 的回复规则学习正则匹配、捕获和替换。 + +项目规则在 `config/chat_rules.toml` 中配置,公开示例见 [`config/chat_rules.toml.example`](../../config/chat_rules.toml.example),匹配引擎位于 `src/quickquip/chat/text_rules.py`。§1–§5 学习语法,§6 介绍如何把正则配置成机器人规则。 + +### 先运行一个练习 + +准备 Python 3.11 及以上版本,`re` 是 Python 自带模块。打开终端,进入准备存放练习的文件夹,运行 `python3 --version` 确认版本;Windows 使用已安装的 Python 时可用 `py --version`,使用 QuickQuip 懒人包时,在解压根目录用 PowerShell 执行 `.\python\python.exe --version`。 + +用文本编辑器在这个文件夹创建 `regex_practice.py`,以 UTF-8 保存下面的完整代码。Windows 保存时确认文件名为 `.py`,而非 `.py.txt`。 + +```python +import re + +pattern = r"玩(?P.+?)玩的" +text = "玩原神玩的" +match = re.search(pattern, text) +print("是否匹配:", bool(match)) +if match: + print("匹配的文本:", match.group(0)) + print("捕获的内容:", match.groups()) +``` + +在同一文件夹运行 `python3 regex_practice.py`;Windows 对应使用 `py regex_practice.py`,懒人包使用 `.\python\python.exe regex_practice.py`。输出如下,其中最后一行列出捕获到的内容: + +```text +是否匹配: True +匹配的文本: 玩原神玩的 +捕获的内容: ('原神',) +``` + +后续练习可以替换这里的 `pattern` 和 `text` 后重新运行。形如 `r"..."` 的单行片段只展示模式,填入 `pattern =` 后使用;带 `import re` 的完整教学示例可替换整个文件。标有“源码位置”的片段用于理解项目实现,TOML 片段则按 §6 合入机器人配置。 --- @@ -27,12 +53,12 @@ ## 1. 什么是正则表达式? -**正则表达式**(Regular Expression,简称 regex 或 regexp)是一种用来描述“文本模式”的微型语言。你可以把它想象成一个**超级升级版的搜索功能**: +**正则表达式**(Regular Expression,简称 regex 或 regexp)是一种用来描述“文本模式”的微型语言。你可以把它想象成一个能描述文本形状的搜索功能: - 普通搜索:在文本中找“猫” → 只能精确匹配“猫”这个字 - 正则搜索:在文本中找“任意一个汉字重复两次后跟`你的`” → 能匹配“牛牛你的”“哈哈你的”“嘿嘿你的”…… -在 QuickQuip 项目中,正则表达式是**规则引擎的核心**。机器人收到一条群聊消息后,会依次用多个正则表达式去“试探”这条消息是否匹配某个趣味回复规则。一旦匹配成功,就提取关键信息、填入模板、发送回复。 +在 QuickQuip 项目中,正则表达式是**规则引擎的核心**。机器人收到一条群聊消息后,会依次用多个正则表达式去“试探”这条消息是否匹配某个趣味回复规则。匹配后提取信息、填入回复模板,再按规则开关、概率和限流决定是否发送。 ### 一个直观的例子 @@ -43,11 +69,12 @@ r"玩(?P.+?)玩的" ``` 它做了这些事: + 1. 寻找以 `玩` 开头的文本 2. 捕获中间的内容(`原神`),并命名为 `target` 3. 确认以 `玩的` 结尾 -这就是正则表达式的威力——用一条简短的规则,匹配无穷多种输入。 +同一个模式可以提取“玩星铁玩的”“玩原神玩的”等消息中的游戏名。 --- @@ -104,12 +131,12 @@ print(result) # "还在打游戏" 你会注意到项目中的正则表达式都以 `r` 开头。这是 Python 的**原始字符串**(raw string),它会阻止 Python 解释反斜杠转义: ```python -# 不用 r:\d 会被 Python 当作转义序列(虽然 \d 恰好不是有效转义,但 \b 就会出问题) +# 普通字符串中,用双反斜杠保留正则需要的反斜杠 pattern1 = "\\d+" # 需要双反斜杠 pattern2 = r"\d+" # ✅ 推荐写法,所见即所得 ``` -**经验法则:写正则时永远用 `r"..."` 前缀。** +**建议:** 正则模式使用 `r"..."`,让反斜杠按正则语法解释。 --- @@ -134,30 +161,32 @@ rate_limit_key = 'divine_arrival' priority = 100 ``` -`patterns` 用 TOML 字面量字符串(单引号):字面量字符串不处理转义序列,正则里的 `\1`、`\u4e00` 会原样传给 `re` 编译——等价于 Python 的 `r'...'`。若用双引号基本字符串,转义序列 `\u4e00` 会被 TOML 解码成实际汉字(正则仍然可用),但 `\1` 是非法转义会直接报解析错误,所以含反向引用的模式必须用单引号。任意一条 pattern 命中即触发。 +`patterns` 用 TOML 字面量字符串(单引号):字面量字符串不处理转义序列,正则里的 `\1`、`\u4e00` 会原样传给 `re` 编译——等价于 Python 的 `r'...'`。若用双引号基本字符串,转义序列 `\u4e00` 会被 TOML 解码成实际汉字(正则仍然可用),`\1` 会导致 TOML 解析失败。使用单引号字面量字符串,可以直接书写正则的反斜杠;双引号字符串则需要把反斜杠写成 `\\`。任意一条 pattern 命中,即可进入后续触发检查。 ### 3.2 锚点——限定匹配位置 | 符号 | 含义 | 示例 | |------|------|------| | `^` | 字符串**开头** | `^我` 匹配以“我”开头的文本 | -| `$` | 字符串**结尾** | `的$` 匹配以“的”结尾的文本 | +| `$` | 字符串结尾,或末尾换行符之前 | `的$` 匹配以“的”结尾的文本 | -当 `^` 和 `$` 同时出现时,要求**整个字符串**完全符合模式: +对于单行消息,`^` 与 `$` 可以限定匹配范围: ```python -r"^我喜欢(.+)$" # 整条消息必须是“我喜欢...”的格式 +r"^我喜欢(.+)$" # 匹配“我喜欢...”格式的单行消息 ``` +需要严格检查整条字符串时,使用 `re.fullmatch()`,见 §8.3。 + ### 3.3 字符类——匹配一类字符 | 语法 | 含义 | 示例 | |------|------|------| | `[abc]` | 匹配 a、b 或 c 中的任意一个 | `[??]` 匹配中文或英文问号 | | `[a-z]` | 匹配 a 到 z 的任意小写字母 | | -| `[\u4e00-\u9fa5]` | 匹配任意一个**中文汉字** | 这是 Unicode 范围 | +| `[\u4e00-\u9fa5]` | 匹配此 Unicode 范围内的一个汉字 | 范围说明见 §8.4 | | `.` | 匹配**任意字符**(换行符除外) | `玩.+?玩的` | -| `\d` | 匹配数字 `[0-9]` | `\$(\d+)` 匹配 `$1`、`$2` | +| `\d` | 匹配 Unicode 十进制数字;ASCII 数字用 `[0-9]` | `\$(\d+)` 匹配 `$1`、`$2` | | `\s` | 匹配空白字符(空格、制表符等) | `[,,]\s*` | 项目中汉字范围 `[\u4e00-\u9fa5]` 出现了多次: @@ -185,8 +214,8 @@ r"^我(?P[\u4e00-\u9fa5]{2})[!!。,,??]*$" 默认情况下,量词是**贪婪**的——尽可能多地匹配: ```python -r"玩(.+)玩的" # 贪婪:输入“玩A玩B玩的”会匹配到“A玩B” -r"玩(.+?)玩的" # 非贪婪(加 ?):匹配到“A”就停止 +r"玩(.+)玩的" # 输入“玩A玩的,玩B玩的”时捕获“A玩的,玩B” +r"玩(.+?)玩的" # 相同输入捕获“A”,在第一个“玩的”前停止 ``` 在量词后加 `?` 可以切换为**非贪婪**模式。QuickQuip 中的 `play_target` 规则就使用了非贪婪匹配: @@ -231,7 +260,7 @@ priority = 100 | `我神临了` | ✅ | 子串匹配 | | `神来了` | ❌ | 不包含“神临” | -> **要点:** 引擎用 `search()` 匹配,默认搜索子串。如果要求整条消息完全等于某个模式,需要加 `^` 和 `$` 锚点。 +> **要点:** 引擎用 `search()` 搜索子串。单行规则可用 `^` 和 `$` 限定范围;严格检查整个字符串时用 `fullmatch()`。 ### 4.2 锚点 + 捕获组——`like_reply` 规则 @@ -257,6 +286,7 @@ priority = 60 **关键概念——捕获组 `(...)`:** 圆括号将匹配到的内容“捕获”起来,存入编号组中: + - `$0` / `group(0)`:整个匹配结果 - `$1` / `group(1)`:第一个括号捕获的内容 - `$2` / `group(2)`:第二个括号捕获的内容…… @@ -312,9 +342,10 @@ print(m.groupdict()) # {"target": "原神"} **关键概念——非贪婪 `.+?`:** -如果使用贪婪的 `.+`,面对 `玩王者玩原神玩的` 这种输入: -- `.+`(贪婪)→ 捕获 `王者玩原神` -- `.+?`(非贪婪)→ 捕获 `王者`(遇到第一个“玩的”就停止) +如果使用贪婪的 `.+`,面对 `玩王者玩的,玩原神玩的` 这种输入: + +- `.+`(贪婪)捕获 `王者玩的,玩原神`。 +- `.+?`(非贪婪)捕获 `王者`,让后面的“玩的”匹配第一个结束位置。 ### 4.4 反向引用——`double_char_ni_de` 规则 @@ -340,15 +371,15 @@ priority = 80 **关键概念——反向引用 `\1`:** -`\1` 不是“再匹配一个汉字”,而是“匹配与第 1 个捕获组**完全相同**的内容”。这保证了两个字必须一模一样。 +`\1` 匹配与第 1 个捕获组**完全相同**的内容,因此前两个汉字必须相同。 ```python import re # ✅ 匹配:两个“牛”是相同的 -re.search(r"^([\u4e00-\u9fa5])(\1)你的$", "牛牛你的") +print(bool(re.search(r"^([\u4e00-\u9fa5])(\1)你的$", "牛牛你的"))) # True # ❌ 不匹配:“牛”和“马”不同 -re.search(r"^([\u4e00-\u9fa5])(\1)你的$", "牛马你的") +print(bool(re.search(r"^([\u4e00-\u9fa5])(\1)你的$", "牛马你的"))) # False ``` | 输入 | 匹配? | `$1` | 回复 | @@ -381,6 +412,7 @@ priority = 75 ``` 这个“三明治”结构要求: + 1. 开头一个汉字 A 2. 中间至少两个字符 B(被捕获为 `$2`) 3. 再出现相同的汉字 A @@ -411,7 +443,7 @@ rate_limit_key = 'new_three_kingdoms' priority = 87 ``` -**正则分析:** 这条新三国规则展示了三种捕获方式的协同——命名捕获组 `(?P...)` 提取人名,字符类选择 `(称帝|撑地)` 是一个普通捕获组(第 2 组),模板里 `{person}` 与 `$2` 混用,各自引用。 +**正则分析:** 这条新三国规则展示了三种捕获方式的协同——命名捕获组 `(?P...)` 提取人名,分支 `(称帝|撑地)` 是一个普通捕获组(第 2 组),模板里 `{person}` 与 `$2` 混用,各自引用。 ``` 恭喜(?P.+?)可以(称帝|撑地)了 @@ -427,7 +459,7 @@ priority = 87 | `恭喜刘禅可以撑地了` | `刘禅` | `撑地` | `恭喜刘禅可以撑地了` | | `恭喜曹丕登基了` | — | — | 不匹配(缺“可以”和分支词) | -> **历史教学案例(非仓库规则):** 曾有规则使用 `(?:的)?` 这样的**非捕获组 + 可选**结构——只分组不占用捕获组编号,在多捕获组规则里避免打乱 `$1`、`$2` 的编号。需要该技巧时可参考本节把 `(称帝|撑地)` 换成 `(?:称帝|撑地)` 对比理解:前者可用 `$2` 引用,后者不占编号。 +**非捕获组** `(?:...)` 用于组织模式,捕获组编号保持原有顺序。例如 `(?:的)?` 表示可选的“的”。本节的 `(称帝|撑地)` 需要用 `$2` 生成回复,因此使用普通捕获组。 ### 4.7 命名捕获组 + 黑名单过滤——`i_do` 规则 @@ -458,7 +490,7 @@ verb = [ └──── ^ 开头 + 字面“我” ``` -这条规则的巧妙之处在于它结合了**正则匹配**和**程序逻辑过滤**: +这条规则分两步判断: 1. 正则部分:匹配“我” + 两个汉字 + 可选标点 2. 程序部分:`[rules.blocked_named_groups]` 声明捕获组 `verb` 的黑名单,引擎在 `is_rule_match_allowed()`(`text_rules.py`)里检查命中的 `verb` 是否在列表中,命中则不触发、继续尝试下一条规则 @@ -471,7 +503,7 @@ verb = [ | `我觉得` | ✅ verb=`觉得` | **在黑名单** | ❌ | 不回复 | | `我ABC` | ❌ | — | ❌ | 非汉字不匹配 | -**模板中的 `$1`:** 虽然使用了命名捕获组 `(?P...)`,但 `$1` 仍然有效——命名捕获组同时拥有名称和数字编号。 +**模板中的 `$1`:** 命名捕获组同时拥有名称和数字编号,`(?P...)` 在这里既可以用 `{verb}` 引用,也可以用 `$1` 引用。 按组号索引的黑名单(`blocked_groups`)用法相同,位置捕获组规则可用。 @@ -498,9 +530,12 @@ GOOD_GIRL_START_PATTERN = re.compile(r"^(.+?)是好(.+?)吗[??]*$") 这条正则使用 `re.compile()` 预编译,然后通过 `.fullmatch()` 调用——要求**整条消息**完全匹配模式。 ```python -# .fullmatch() = 隐含了 ^ 和 $(尽管这里已经写了) +import re +GOOD_GIRL_START_PATTERN = re.compile(r"^(.+?)是好(.+?)吗[??]*$") +# fullmatch 要求整个字符串符合模式 start_match = GOOD_GIRL_START_PATTERN.fullmatch("小明是好学生吗?") lead_char = start_match.group(1)[0] # “小”(取第一个字) +print(lead_char) # 小 ``` | 输入 | 匹配? | `group(1)` | `group(2)` | @@ -551,7 +586,7 @@ priority = 95 ### 5.1 `re.sub` 与回调函数——模板引擎的秘密 -QuickQuip 的回复模板中使用 `$1`、`$2` 作为占位符,而 Python 的 `str.format()` 使用 `{}`。项目通过 `re.sub()` 巧妙地桥接了两者。 +QuickQuip 的回复模板中使用 `$1`、`$2` 作为占位符,而 Python 的 `str.format()` 使用 `{}`。项目先用 `re.sub()` 替换编号捕获组,再填入命名变量。 **源码位置:** `src/quickquip/chat/text_rules.py`(`replace_regex_groups` 函数) @@ -602,7 +637,7 @@ context = {**base_context, **match.groupdict()} ```python import re m = re.search(r"玩(?P.+?)玩的", "玩原神玩的") -m.groupdict() # {"target": "原神"} +print(m.groupdict()) # {'target': '原神'} ``` 项目将它与基础上下文合并,使得模板中既可以用 `{target}`(来自正则),也可以用 `{sender_name}`(来自程序)。基础上下文由 `build_rule_context()` 构造,包含三个程序侧变量: @@ -611,10 +646,10 @@ m.groupdict() # {"target": "原神"} base_context = {"current_time": "2026-08-30 14:00", "user_id": "123456", "sender_name": "张三"} regex_context = {"target": "原神"} context = {**base_context, **regex_context} -# {"current_time": ..., "user_id": ..., "sender_name": "张三", "target": "原神"} +print(context) # 包含 current_time、user_id、sender_name 和 target 四项 ``` -### 5.3 预编译与热重载——引擎的现行选择 +### 5.3 预编译与热重载 现行引擎对**全部规则正则统一预编译**:`text_rules.py` 在模块加载时把 `TEXT_REPLY_RULES` 里每条规则的 `patterns` 编译进模块级列表 `_COMPILED_PATTERNS`,匹配主循环只调用 `compiled.search(text)`: @@ -630,9 +665,9 @@ def recompile_patterns() -> None: 注意 `recompile_patterns()` 用切片赋值 `_COMPILED_PATTERNS[:] = ...` **原地重建**列表——持有该列表引用的调用方(匹配主循环)无需重新导入即可看到新规则,这是配置热重载能即时生效的关键(见 §6.6)。 -引擎之外仍有少量固定模式直接预编译为模块常量,例如 `good_girl_chain.py` 的 `GOOD_GIRL_START_PATTERN`、`chain_game.py` 的 `_REF_RE`。 +其他模块也会将固定模式预编译为模块常量,例如 `good_girl_chain.py` 的 `GOOD_GIRL_START_PATTERN`、`chain_game.py` 的 `_REF_RE`。 -> **性能说明:** Python 的 `re` 模块内部有缓存机制(默认缓存最近 512 个模式),即使逐条内联 `re.search` 也不会有明显性能损失;统一预编译的意义更多在于**热重载时能整体换新**,而非单纯的匹配速度。 +预编译让启动和重载时统一检查模式语法,运行时直接使用已编译对象。 --- @@ -640,15 +675,42 @@ def recompile_patterns() -> None: 写对正则只是第一步。一条规则要真正上线,还要放进 `config/chat_rules.toml` 的完整结构里,经过限流、开关、上下文判定等一系列机制。本节是这套体系的速览,权威参考始终是 `config/chat_rules.toml.example` 的注释。 +### 先新增一条规则并验证 + +以下步骤用于已经运行、能接收测试群消息的 QuickQuip。需要修改该实例配置文件的权限,并由测试群的群主、群管理员或[全局管理员](../admin/global-admins.md)执行重载。容器部署时,编辑[挂载到容器的宿主机配置目录](../admin/deployment.md)里的文件;懒人包在解压根目录操作。 + +1. 先复制实际使用的 `config/chat_rules.toml` 作为备份,再用编辑器打开原文件。首次配置且文件尚未创建时,将 `config/chat_rules.toml.example` 复制为 `config/chat_rules.toml`。 +2. 找到已有的 `[rate_limit_rules]` 行,在它之后、下一个以 `[` 开头的配置段之前加入下面这一行。这是向现有表增加一项,保留原有桶。 + + ```toml + regex_tutorial = {global_limit = 6, user_limit = 3} + ``` + +3. 在文件末尾追加下面的完整规则段,以 UTF-8 保存。`regex_tutorial` 是本次示例的名称,文件中已有同名规则或桶时换一个唯一名称,并同步修改上一步的桶名和下方的 `rate_limit_key`。 + + ```toml + [[rules]] + name = 'regex_tutorial' + patterns = ['^正则练习:(?P.+)$'] + reply_template = '收到:{target}' + rate_limit_key = 'regex_tutorial' + priority = 40 + ``` + +4. 在测试群发送 `/reload_rules`,等待机器人回复 `chat_rules 已重载(text … / context … / chain … / rate_limit …)`。这个操作会更新该运行实例的规则配置。 +5. 在同一群发送 `正则练习:原神`,预期回复为 `收到:原神`。该桶默认触发概率为 1;同一用户每 60 秒最多触发 3 次,同一群共用最多 6 次。 + +收到“仅管理员可执行此操作”时,请让上述管理员执行重载。重载报错或 `/rules` 中没有新规则时,检查 Bot 日志中的 TOML 与正则错误;不要重复添加 `[rate_limit_rules]` 表头。没有回复时,用 `/rules` 查看开关,已禁用则由管理员执行 `/enable regex_tutorial`;连续测试达到限流次数时等待 60 秒。出现其他回复时,按下方 `priority` 说明检查规则竞争。 + ### 6.1 `[[rules]]` 字段速查 | 字段 | 必填 | 说明 | |------|------|------| | `name` | ✅ | 规则唯一名称,用于统计(`/stats`)和开关控制(`/disable` / `/enable`) | -| `patterns` | ✅ | 触发正则列表(TOML 字面量字符串,单引号避免反斜杠转义),任意一条命中即触发 | +| `patterns` | ✅ | 触发正则列表(TOML 字面量字符串,单引号避免反斜杠转义),任意一条命中即可进入后续检查 | | `reply_template` | ✅* | 回复模板(与 `reply_templates` 二选一) | | `rate_limit_key` | ✅ | 限流桶名称(需在 `[rate_limit_rules]` 定义,或引用系统预定义桶) | -| `priority` | ✅ | 整数越大越优先;同一消息命中多条规则时只触发最高优先级那条 | +| `priority` | ✅ | 整数越大越优先;同一消息有多条规则通过匹配与概率检查时,优先选择数值最高的规则 | | `reply_templates` | | 加权随机回复列表(见 §6.3) | | `blocked_named_groups` | | 命名捕获组黑名单(见 §4.7) | | `blocked_groups` | | 位置捕获组黑名单,按组号索引,用法同上 | @@ -676,7 +738,7 @@ image_gen = {global_limit = 10, user_limit = 2, scope = "global", window = 60} | `suppress_after_hit` | 防连发:同一规则同一群命中后,接下来 N 次命中强制沉默;默认 0 关闭 | | `pity_step` | 保底步进:连哑越多概率越高(`p_eff = p × (1 + 连哑数 × 步进)`);默认 0 关闭 | -要点:多条规则可共用一个桶(命中任意一条都消耗同一配额);时区、LLM、贴吧、复读、接龙等系统规则的桶已在 `src/quickquip/chat/config.py` 预定义,TOML 里只需定义文字规则专用桶。概率与防连发/保底的完整语义(掷骰时机、按规则×群的状态隔离、状态机类桶不建议配概率、系统预定义桶覆写需整条重写等)见 `docs/admin/configuration.md` 的「自动回复概率」节。 +多条规则可共用一个桶,发送任意一条回复都消耗同一配额。时区、LLM、贴吧、复读、接龙等系统规则的桶已在 `src/quickquip/chat/config.py` 预定义,TOML 里只需定义文字规则专用桶。概率与防连发/保底的完整语义(掷骰时机、按规则×群的状态隔离、状态机类桶不建议配概率、系统预定义桶覆写需整条重写等)见[配置参考的自动回复概率一节](../admin/configuration.md#自动回复概率)。 ### 6.3 `reply_templates` 加权随机回复 @@ -707,7 +769,7 @@ weight = 1 两种类型: - `regex_context`:`context_conditions` 是上下文条件正则列表,需要在最近 `context_window` 条消息(默认 5)里搜到任意一条匹配才放行。**留空视为不放行**(该规则永不触发,模块加载时会打 warning)。 -- `llm_context`:`llm_judge_prompt` 让 LLM 结合最近群聊记录判断语境,只输出 `{"trigger": true/false}` JSON;`llm_timeout`(默认 2.0s,超时视为不触发)与 `llm_cache_ttl`(按规则+群+文本缓存判定结果,默认 60s)控制成本。 +- `llm_context`:`llm_judge_prompt` 让 LLM 结合最近群聊记录判断语境,输出包含布尔值 `trigger` 的 JSON,例如 `{"trigger": true}`;`llm_timeout`(默认 2.0s,超时视为不触发)与 `llm_cache_ttl`(按规则+群+文本缓存判定结果,默认 60s)控制成本。 字段细节与示例见 `config/chat_rules.toml.example` 的 `[[context_rules]]` 段(新三国梗里有 7 条实战配置可参考)。 @@ -751,7 +813,7 @@ rate_limit_key = 'good_girl_chain_entry' ```text 修改 toml 文件 │ - ├── 群里执行 /reload_rules(或 /reload_personas 重载人格) + ├── 群内管理员执行 /reload_rules └── Web Admin 在线编辑并保存规则文件 │ ▼ @@ -770,7 +832,7 @@ recompile_patterns() ← text_rules.py,_COMPILED_PATTERNS[:] 原地重 ## 7. 项目中的正则表达式全景索引 -**配置侧正则的权威清单是 `config/chat_rules.toml.example`**:共 32 条命名规则——25 条 `[[rules]]`(含 18 条新三国 `ntk_*`)+ 7 条 `[[context_rules]]`(新三国语境判定)。部署方私有规则不在公开仓库。本文不逐一复制该清单(避免双份维护漂移),只索引**引擎与代码侧**的正则: +**配置侧的完整规则清单见 [`config/chat_rules.toml.example`](../../config/chat_rules.toml.example)**,包括普通规则、新三国语境判定与接龙示例。下面列出引擎与代码侧模式,方便继续阅读实现: | 位置 | 正则 | 用途 | |------|------|------| @@ -796,7 +858,7 @@ pattern = "我\b" pattern = r"我\b" ``` -TOML 侧同理:patterns 要用单引号字面量字符串(`'^我喜欢(.+)$'`)。双引号基本字符串里转义序列 `\u4e00` 会被 TOML 解码成实际汉字(正则仍然可用),而 `\1` 这类反向引用是 TOML 非法转义,会直接解析失败。 +TOML 侧同理:patterns 推荐用单引号字面量字符串(`'^我喜欢(.+)$'`)。双引号基本字符串里转义序列 `\u4e00` 会被 TOML 解码成实际汉字(正则仍然可用),而 `\1` 这类反向引用是 TOML 非法转义,会直接解析失败。 ### 8.2 贪婪匹配导致的意外 @@ -804,53 +866,53 @@ TOML 侧同理:patterns 要用单引号字面量字符串(`'^我喜欢(.+)$' import re # 贪婪:匹配到最后一个“玩的” -re.search(r"玩(.+)玩的", "玩A玩B玩的").group(1) -# 结果:“A玩B” —— 可能不是你想要的 +print(re.search(r"玩(.+)玩的", "玩A玩的,玩B玩的").group(1)) +# 结果:“A玩的,玩B” # 非贪婪:匹配到第一个“玩的” -re.search(r"玩(.+?)玩的", "玩A玩B玩的").group(1) -# 结果:“A” —— 通常更符合预期 +print(re.search(r"玩(.+?)玩的", "玩A玩的,玩B玩的").group(1)) +# 结果:“A” ``` **经验法则:** 当捕获的内容“比预期多”时,检查是否应该使用非贪婪量词 `+?` 或 `*?`。 ### 8.3 `search` vs `match` vs `fullmatch` -| 方法 | 行为 | 等价写法 | -|------|------|---------| -| `re.search(p, s)` | 在字符串**任意位置**找第一个匹配 | — | -| `re.match(p, s)` | 只从字符串**开头**匹配 | `re.search(r"^" + p, s)` | -| `re.fullmatch(p, s)` | 要求**整个字符串**完全匹配 | `re.search(r"^" + p + r"$", s)` | +| 方法 | 行为 | +|------|------| +| `re.search(p, s)` | 在字符串任意位置找第一个匹配 | +| `re.match(p, s)` | 从字符串开头匹配 | +| `re.fullmatch(p, s)` | 要求整个字符串完全匹配 | + +`$` 可以匹配末尾换行符之前,而 `fullmatch` 要求匹配实际结束位置。例如 `re.search(r"^猫$", "猫\n")` 可以命中,`re.fullmatch(r"猫", "猫\n")` 返回 `None`。锚点与匹配方法的完整语义见 [Python `re` 文档](https://docs.python.org/3.11/library/re.html)。 ```python import re text = "我喜欢编程" -re.search(r"喜欢", text) # 匹配成功(子串匹配) -re.match(r"喜欢", text) # 不匹配(开头不是“喜欢”) -re.fullmatch(r"喜欢", text) # 不匹配(整个字符串不等于“喜欢”) +print(bool(re.search(r"喜欢", text))) # True:匹配成功(子串匹配) +print(bool(re.match(r"喜欢", text))) # False:不匹配(开头不是“喜欢”) +print(bool(re.fullmatch(r"喜欢", text))) # False:不匹配(整个字符串不等于“喜欢”) -re.match(r"我喜欢", text) # 匹配成功(开头匹配) -re.fullmatch(r"我喜欢编程", text) # 匹配成功(完全匹配) +print(bool(re.match(r"我喜欢", text))) # True:匹配成功(开头匹配) +print(bool(re.fullmatch(r"我喜欢编程", text))) # True:匹配成功(完全匹配) ``` QuickQuip 中的选择: + - TOML 规则统一走 `compiled.search()`,锚定由规则作者用 `^`、`$` 显式控制 -- `good_girl_chain.py` 使用 `re.compile().fullmatch()`,是一种等价的风格选择 +- `good_girl_chain.py` 使用 `re.compile().fullmatch()`,要求完整匹配接龙触发消息 ### 8.4 Unicode 汉字范围的局限 -`[\u4e00-\u9fa5]` 覆盖了 CJK 统一汉字基本区(20,902 个字符),但不包括: -- 扩展区 A(`㐀-䶿`) -- 扩展区 B 及以后(需要代理对) -- 兼容汉字 - -对于群聊机器人来说,基本区已经覆盖了日常使用的绝大多数汉字,因此足够使用。STS 被动公式用的 `[一-鿿]` 是同一基本区的另一种写法。 +`[\u4e00-\u9fa5]` 包含 20,902 个码位,覆盖常用汉字。STS 模式中的 `[一-鿿]` 对应 U+4E00–U+9FFF,结束位置更靠后。这两种范围都限于 CJK 统一汉字基本区;需要匹配扩展区或兼容汉字时,应按任务扩展字符范围。Python 3 字符串可直接表示扩展区汉字。 ### 8.5 调试正则的实用方法 -**方法 1:Python 交互式环境** +**方法 1:在练习文件中批量测试** + +将下面代码保存到开篇的 `regex_practice.py`,按同样的命令运行,输出每条输入是否匹配及其捕获组。 ```python import re @@ -880,9 +942,10 @@ pattern = re.compile(r""" [!!。,,??]* # 可选的中英文标点 $ # 结尾 """, re.VERBOSE) +print(pattern.fullmatch("吃饭!").group("verb")) # 吃饭 ``` -`re.VERBOSE` 模式忽略空白和 `#` 注释,让复杂正则更易读。(注意:TOML 规则的 patterns 不支持 VERBOSE——需要注释时写在 TOML 的 `#` 注释行里。) +`re.VERBOSE` 模式忽略空白和 `#` 注释,让复杂正则更易读。TOML 模式可用内联标志 `(?x)` 启用该模式;日常配置说明写在 TOML 的 `#` 注释行里。 **方法 4:真机验证** @@ -902,16 +965,19 @@ pattern = re.compile(r""" # 提示:使用 re.IGNORECASE 标志 import re pattern = r"yyds" -re.search(pattern, "这个真的是YYDS", re.IGNORECASE) +print(bool(re.search(pattern, "这个真的是YYDS", re.IGNORECASE))) # True ```
参考答案 ```python -r"(?i)yyds" -# 或者 -re.search(r"yyds", text, re.IGNORECASE) +import re + +text = "这个真的是YYDS" +print(bool(re.search(r"(?i)yyds", text))) # True +# 等价写法 +print(bool(re.search(r"yyds", text, re.IGNORECASE))) # True ``` `(?i)` 是内联标志,等价于 `re.IGNORECASE`。 @@ -934,7 +1000,7 @@ re.search(r"yyds", text, re.IGNORECASE) r"^(.+?)太强了[!!]*$" ``` -使用非贪婪 `.+?` 防止过度匹配,末尾允许可选感叹号。 +用 `.+?` 捕获主题,末尾允许可选感叹号。
@@ -978,15 +1044,13 @@ rate_limit_key = 'compare_reply' priority = 40 ``` -别忘了限流桶要先在 `[rate_limit_rules]` 定义(或复用现成桶如 `group_meme`)。 - -> **注意:** 纯正则无法验证“两个捕获组内容不同”这一约束。如果需要这个逻辑,可以参考 `i_do` 规则的方式,在 `blocked_named_groups` 中做程序级过滤。 +将 `compare_reply` 桶合入已有 `[rate_limit_rules]` 表,把 `[[rules]]` 段追加到规则列表。也可以让规则复用 `group_meme` 等已有桶。 ### 练习 5:理解执行流程(难度 ★★★★★) -阅读下面的代码(摘自现行 `src/quickquip/chat/text_rules.py`,略有精简),回答问题: +下面的教学代码保留规则匹配、捕获与优先级排序;实际引擎还会进行概率检查。阅读后回答问题: ```python def match_text_rule(text, user_id, sender_name, now=None): @@ -1010,12 +1074,14 @@ def match_text_rule(text, user_id, sender_name, now=None): }) break matched_rules.sort(key=lambda item: (-item["priority"], item["rule_index"])) + if not matched_rules: + return None best_match = matched_rules[0] best_match.pop("rule_index", None) return best_match ``` -**问题:** 如果一条消息同时命中了 `divine_arrival`(priority=100,配置文件中靠前)和 `ntk_nizoule`(priority=100,配置文件中靠后),最终会触发哪条规则?为什么? +**问题:** 假设两条规则均已通过匹配和概率检查,一条消息同时命中了 `divine_arrival`(priority=100,配置文件中靠前)和 `ntk_nizoule`(priority=100,配置文件中靠后),最终会触发哪条规则?为什么?
参考答案 @@ -1057,8 +1123,8 @@ def match_text_rule(text, user_id, sender_name, now=None): | `{n,m}` | n 到 m 次 | `a{2,4}` 匹配 `aa`、`aaa`、`aaaa` | | `[abc]` | 字符类 | `[aeiou]` 匹配元音 | | `[^abc]` | 否定字符类 | `[^0-9]` 匹配非数字 | -| `\d` | 数字 `[0-9]` | | -| `\w` | 单词字符 `[a-zA-Z0-9_]` | | +| `\d` | Unicode 十进制数字,ASCII 数字用 `[0-9]` | | +| `\w` | Unicode 字母、数字与下划线 | | | `\s` | 空白字符 | | | `\b` | 单词边界 | | | `(...)` | 捕获组 | | @@ -1071,6 +1137,6 @@ def match_text_rule(text, user_id, sender_name, now=None): > **文档信息** > -> - 本文档基于 QuickQuip 项目编写,代码示例均来自项目实际源码与 `config/chat_rules.toml.example` +> - 项目规则案例依据实际源码与 `config/chat_rules.toml.example`;入门演示与练习题为教学示例 > - 适用 Python 版本:≥ 3.11 -> - 最后更新:2026-08-30 +> - 最后核对:2026-10-14 diff --git a/skills.example/self-docs/references/docs-dev-skill-tutorial.md b/skills.example/self-docs/references/docs-dev-skill-tutorial.md index e618702c..357b4632 100644 --- a/skills.example/self-docs/references/docs-dev-skill-tutorial.md +++ b/skills.example/self-docs/references/docs-dev-skill-tutorial.md @@ -2,11 +2,9 @@ # 从零开始编写 Skill —— 以 QuickQuip 项目为例 -> **面向读者:** 想给机器人扩充"领域知识包"的部署者(§1–§4,零编程门槛,只需要会编辑文本文件和复制目录),以及想给 Skill 挂脚本的开发者(§5,需要 Python 基础)。 -> -> **前置要求:** §1–§4 无编程要求;§5 需要了解 Python 基础语法(函数、字典、标准库导入)。 -> -> **源码指引:** Skill 系统实现位于 `src/quickquip/llm/skills/`(frontmatter 校验 `parser.py`、目录扫描与路径加固 `catalog.py`、预置漂移检测 `preset_drift.py`、四个工具 `tools/`),工具注册与激活门控位于 `src/quickquip/llm/service_parts/skills.py`,`/skill` 命令位于 `src/quickquip/adapters/nonebot/command_parts/skills.py`。部署与安全模型的权威文档是 [docs/admin/skills.md](../admin/skills.md),配置键与默认值见 `config/llm.toml.example` 的 `[skills]` 段。 +本教程帮助部署者安装和编写 Skill,为机器人补充领域知识。§1–§4 只需要编辑文本文件和复制目录;§5 面向希望附带脚本的开发者,需要了解 Python 函数、字典和标准库。 + +开始前,请确认 Bot 正在运行且已能进行 AI 对话,Web 管理后台已启动并可登录,你能写入 Bot 使用的 Skill 目录。部署、启用与安全要求见 [Skill 部署说明](../admin/skills.md)。本文的复制命令使用 Bash,按 §3.1 确定执行目录;Windows 部署者可用文件管理器完成同样的目录创建和复制。实现入口列在文末。 --- @@ -24,19 +22,19 @@ ## 1. 什么是 Skill? -**Skill 是一段随机器人部署的"领域知识包"。** 部署者把一个文件夹放进 `skills/` 目录,机器人的 AI 在群聊里遇到与该 Skill 描述匹配的请求时自主激活它,然后按其中的指引查阅附带资料、回答问题,必要时运行附带脚本。典型用途:让 AI 基于内置文档副本回答机器人用法提问(`skills.example/self-docs`)、汇报部署主机健康状态(`skills.example/host-healthcheck`)。 +**Skill 是随机器人部署的“领域知识包”。** 部署者把一个文件夹放进 `skills/` 目录,机器人的 AI 在群聊里遇到与该 Skill 描述匹配的请求时自主激活它,然后按其中的指引查阅附带资料、回答问题,必要时运行附带脚本。典型用途:让 AI 基于内置文档副本回答机器人用法提问(`skills.example/self-docs`)、汇报部署主机健康状态(`skills.example/host-healthcheck`)。 -Skill 的格式遵循 Agent Skills 开放标准的可移植核心:一个子目录 + 一份 YAML frontmatter 的 `SKILL.md`(`src/quickquip/llm/skills/parser.py`)。 +每个 Skill 占一个子目录,核心文件是 `SKILL.md`:开头用 YAML frontmatter 记录名称和描述,后面用 Markdown 编写指令。 -它的三条设计动机: +编写时需要理解三个原则: -- **部署者受信安装。** Skill 的指令正文会进入对话上下文,脚本会在部署主机上执行,因此入口只有部署者的文件操作。运行时没有任何创建、修改或删除 Skill 的路径,AI 侧不存在"安装 Skill"的工具。 -- **AI 遇匹配请求自主激活。** 系统提示里常驻的只有每个 Skill 的 `name + description` 清单(路由信息)。模型判断当前请求与某条描述匹配时调用 `activate_skill`;不匹配就当它不存在,群友日常聊天感知不到它。 +- **部署者受信安装。** Skill 的指令正文会进入对话上下文,脚本会在部署主机上执行,因此由部署者通过部署机器或管理后台安装、更新和删除。 +- **AI 遇匹配请求自主激活。** 系统提示里常驻的只有每个 Skill 的 `name + description` 清单(路由信息)。模型判断当前请求与某条描述匹配时调用 `activate_skill`;其他请求按常规对话处理。 - **按需逐层加载。** 第一层:name + description 常驻系统提示,成本极小;第二层:激活时才注入 `SKILL.md` 正文与文件清单;第三层:正文指引 AI 用资源工具按需读取单个文件、检索关键词、运行脚本。大量参考材料放在 `references/` 里不必挤进上下文,用到哪份读哪份。 ### 1.1 Skill 在 QuickQuip 能力体系中的位置 -每轮请求构建系统提示时,运行器现扫 `skills/` 目录,把通过校验的 Skill 清单渲染成一个 `` 块挂在系统提示静态段末尾(`src/quickquip/llm/skills/context.py` 的 `render_catalog_block`)。块内只有 name 和 description,永不含正文与宿主机路径。示意: +开启工具调用和 Skill 后,机器人每轮扫描 `skills/`,将通过校验的名称与描述写入系统提示的 `` 清单。模型用这份简短清单选择要激活的 Skill,激活时再加载正文。示意: ```text @@ -46,20 +44,20 @@ Skill 的格式遵循 Agent Skills 开放标准的可移植核心:一个子目 ``` -与清单配套的是四个模型工具(`src/quickquip/llm/service_parts/skills.py` 注册): +AI 使用四个工具完成激活与资料访问。下面的工具名称用于编写 Skill 指引;群友向机器人发送自然语言问题即可。 -| 工具 | 行为 | +| 工具 | 输入与结果 | |------|------| -| `activate_skill` | 激活一个已安装 Skill,把 `SKILL.md` 正文以 `[skill_activation]` 标记块注入会话尾部;同会话同内容自动去重 | -| `read_skill_resource` | 读取**已激活** Skill 目录内的单个 UTF-8 文本文件,支持按行段分块读取 | -| `search_skill_resources` | 在**已激活** Skill 目录内按关键词或正则检索文本,返回 `file:line` 命中与上下文 | -| `run_skill_script` | 执行**已激活** Skill `scripts/` 下的 `.py` / `.sh` 脚本 | +| `activate_skill` | `name` 填已安装的 Skill 名;返回其指令正文,同会话同内容自动去重 | +| `read_skill_resource` | `skill` 填已激活的 Skill 名,`path` 填包内路径(如 `references/memes.md`);返回 UTF-8 文本,支持按行段读取 | +| `search_skill_resources` | `skill` 填已激活的 Skill 名,`query` 填关键词或正则;返回文件路径、行号和命中上下文 | +| `run_skill_script` | `skill` 填已激活的 Skill 名,`path` 填 `scripts/` 下的 `.py` / `.sh` 文件;可选 `args` 传参数,返回输出、错误信息和退出码 | -后三个工具共享同一道激活门(`src/quickquip/llm/skills/tools/activate.py` 的 `require_active_skill`):目标 Skill 必须既在目录里、又在本会话激活过,缺一不可。 +读取、检索和执行前,目标 Skill 必须已安装,并在当前会话中激活。 -### 1.2 零影响原则 +### 1.2 启用条件 -`[skills] enabled = false`,或 `skills/` 目录为空、不存在时:四个工具不注册,catalog 块渲染为空串,系统提示保持逐字节不变。未部署 Skill 的实例,其行为与没有这套系统的实例完全一致(`src/quickquip/llm/service_parts/skills.py` 的空目录短路逻辑)。 +工具调用与 Skill 均开启、且目录内有可用 Skill 时,模型才能看到清单与 Skill 工具。启用步骤见 §3.2。 --- @@ -78,11 +76,11 @@ skills/ ← 部署目录(项目根,已被 git 忽略) └── scripts/ ← 可选:可执行脚本(只有这个目录下的文件能被 run_skill_script 执行) ``` -目录内所有常规文件都会编入资源清单,按顶层目录名归类为 `reference` / `asset` / `script` / `other`(`src/quickquip/llm/skills/catalog.py` 的 `classify_resource`)。`references/`、`assets/`、`scripts/` 只是约定归类——放错位置的文件照样能被读取检索,但 `scripts/` 之外的一切都不可执行。 +附带常规文件按所在目录归为参考资料、资产、脚本或其他资源。读取和检索工具可以访问这些文本文件;执行工具接受 `scripts/` 中的 `.py` 和 `.sh` 文件。资源数量上限见 §2.3。 ### 2.2 SKILL.md 校验规则 -`SKILL.md` 必须以一行 `---` 开头,中间是 YAML frontmatter,再以单独一行 `---` 收尾,随后是指令正文。校验规则(全部落在 `src/quickquip/llm/skills/parser.py`): +`SKILL.md` 必须以一行 `---` 开头,中间是 YAML frontmatter,再以单独一行 `---` 收尾,随后是指令正文。校验规则如下: | 校验项 | 规则 | 失败后果 | |--------|------|---------| @@ -97,7 +95,7 @@ skills/ ← 部署目录(项目根,已被 git 忽略) ### 2.3 数值上限一览 -| 项 | 默认值 | 配置键(`config/llm.toml` `[skills]`) | +| 项 | 默认值 | 配置键(`data/llm_config.db` `settings.skills`) | |----|--------|------| | 系统提示清单字节预算 | 8192 字节;实际预算取 min(模型上下文窗口 2%,此值) | `catalog_max_bytes` | | 单次读取资源 | 65536 字节(64KiB),超限截断前段 | `resource_max_bytes` | @@ -106,9 +104,9 @@ skills/ ← 部署目录(项目根,已被 git 忽略) | 脚本默认超时 | 30000ms;单次调用可覆盖,硬上限 120000ms | `script_timeout_ms` | | 脚本输出 | stdout / stderr 各 65536 字节,超限截断并终止脚本 | `script_max_output_bytes` | -代码内固定的硬限额:每个 Skill 资源条数 ≤ 200(超出部分不编入清单);检索单文件只读前 1MiB;检索词 ≤ 200 字符;`SKILL.md` ≤ 256KiB、`description` ≤ 1024 字符(`src/quickquip/llm/skills/catalog.py`、`tools/search_resource.py`)。 +代码内固定的硬限额:每个 Skill 资源条数 ≤ 200(超出部分不编入清单);检索单文件只读前 1MiB;检索词 ≤ 200 字符;`SKILL.md` ≤ 256KiB、`description` ≤ 1024 字符。 -清单预算超限时的降级序(`src/quickquip/llm/skills/catalog.py` 的 `_apply_budget`):先把所有 description 统一截短到 160 字符,仍超再截到 80 字符,仍超则按 name 字典序保前弃后淘汰整条(被淘汰者当轮不可激活),永不淘汰到空。系统提示中会注明"另有 N 个 Skill 因目录预算超限未列出"。 +清单超过预算时,机器人依次把描述截短到 160 字符、80 字符,再按名称字典序移除靠后的条目,至少保留一个。被移除的条目当轮无法激活,系统提示会注明“另有 N 个 Skill 因目录预算超限未列出”。 --- @@ -116,33 +114,45 @@ skills/ ← 部署目录(项目根,已被 git 忽略) ### 3.1 安装 -运行目录为项目根的 `skills/`(已被 git 忽略);仓库随附的 `skills.example/` 是官方模板。安装就是文件操作: +先确认文件要放在哪台机器、哪个目录。在 Web 管理后台打开“LLM 工坊 → LLM 配置 → 工具与技能 → Skill 运行时”,查看“Skill 目录”:留空时使用 Bot 项目根的 `skills/`;非空时使用指定目录,相对路径以 Bot 项目根为基准。远程部署应把文件放到运行 Bot 的服务器。 -```bash -# 从官方模板复制(在项目根执行) -cp -r skills.example/self-docs skills/ # 装一个 -cp -r skills.example/. skills/ # 全装 +版本目录部署使用部署根共享的 `skills/`,与 `current` 指向的代码目录分开。容器部署先确认 Bot 和 Web Admin 已挂载同一份 `skills/`;主机目录与容器目录的对应关系见 [Skill 部署说明](../admin/skills.md#部署目录)。下文以默认目录为例,自定义目录时替换命令的目标路径。 -# 或自建目录 -mkdir -p skills/group-meme-pedia/references +仓库随附的 `skills.example/` 是官方模板。源码部署在项目根执行以下 Bash 命令,先安装一个无脚本预置包。版本目录部署在部署根操作,将下文复制命令的来源 `skills.example/` 换为 `current/skills.example/`,目标仍是共享 `skills/`;例如 `cp -r current/skills.example/self-docs skills/`。 + +```bash +mkdir -p skills +cp -r skills.example/self-docs skills/ ``` -目录在每轮构建系统提示时现扫一次,**无需重启**:放入或删掉 Skill 后,进行中会话的下一轮请求即可看到变化。Docker 镜像只含 `skills.example/`,容器化部署经 compose 挂载供给 `skills/`,方式见 `prod.example/` 模板与 [docs/admin/skills.md](../admin/skills.md)。配置键 `catalog_dir` 可把目录指到别处:留空 = 项目根 `skills/`,相对路径按项目根解析。 +Windows 用文件管理器在目标位置新建 `skills` 文件夹,把 `skills.example` 里的整个 `self-docs` 文件夹复制进去,形成 `skills/self-docs/SKILL.md`。已安装同名包时跳过复制,更新方式见管理员文档“预置 Skill”节。 + +也可选择一次安装全部官方包:将上面的单包复制命令换为 `cp -r skills.example/. skills/`,或用文件管理器复制其中的两个子文件夹;两种安装方式任选一种。目录在每轮构建系统提示时现扫一次,放入文件后下一轮请求即可看到变化。 ### 3.2 用 `/skill list` 查看 -群里发送 `/skill list` 可查看已安装 Skill 与当前会话已激活项(只读)。输出形如(`src/quickquip/llm/skills/context.py` 的 `render_skill_list`): +在 Web 管理后台按以下步骤启用: + +1. 打开“LLM 工坊 → LLM 配置 → 工具与技能 → 工具与搜索”,在“工具调用轮次与上限”开启“启用工具调用”。 +2. 切到“Skill 运行时”,开启“启用 Skill”,确认“Skill 目录”与 §3.1 一致。 +3. 默认空工具名单可保持原样。若“工具与搜索 → 内置工具白名单与发现”已有非空“额外启用工具”且“启用方式”为“替换”,将 §1.1 的四个 Skill 工具名加入现有名单,保留其他需要的工具。 +4. 点击“保存草稿”,再点击“应用到 Bot”,等待状态条显示 Bot 已加载刚应用的版本。停在排队状态时,先确认 Bot 进程正在运行。 + +完整配置要求见[Skill 部署说明](../admin/skills.md#配置settingsskills)。 + +群里发送 `/skill list` 可查看已安装 Skill 与当前会话已激活项(只读)。仅安装 `self-docs` 时输出形如: ```text -已安装 Skill(2): -- group-meme-pedia:本群梗百科:群内黑话、绰号、名场面与出处。…… -- host-healthcheck:当用户询问服务器/宿主机健康状态……时使用。…… +已安装 Skill(1): +- self-docs:当用户询问 QuickQuip 机器人的用法、命令、配置或报错时使用。…… 当前会话已激活:(无) ``` -与当前版本预置副本(`skills.example/`)字节级分叉的已安装条目会附带标注「(与当前版本预置不同,可运行 scripts/sync_preset_skills.py 更新)」,提示部署者同步预置更新,见 [docs/admin/skills.md](../admin/skills.md) 的「预置 Skill」节。 +安装数量取决于目录已有内容。再发送 `/ai 你的 /quote 命令怎么用?`,应收到基于文档的用法说明;再次发送 `/skill list`,查看当前会话已激活项中是否出现 `self-docs`。本文使用默认 `/ai` 前缀;已修改前缀的部署使用自己的前缀。AI 对话会请求已配置的模型,费用按渠道计费。 + +与当前版本预置副本(`skills.example/`)字节级分叉的已安装条目会附带标注“(与当前版本预置不同,可运行 scripts/sync_preset_skills.py 更新)”,提示部署者同步预置更新,见 [docs/admin/skills.md](../admin/skills.md) 的“预置 Skill”节。 -`/skill` 只有 `list` 一个子参数,其他写法会收到用法提示。`[skills] enabled = false` 时该命令直接提示功能未启用。 +`/skill` 只有 `list` 一个子参数,其他写法会收到用法提示。`settings.skills.enabled = false` 时该命令直接提示功能未启用。 ### 3.3 激活机制 @@ -170,16 +180,16 @@ mkdir -p skills/group-meme-pedia/references 几个要点: -- `activate_skill` 的 `name` 参数枚举值就是当轮目录名单,模型编不出未安装的名字。 -- 同一会话内重复激活同一 Skill:正文内容未变时只返回"已激活"简短文本,不重复注入;部署者改了 `SKILL.md`,下一轮扫描指纹变化,再激活会注入新正文。 -- 激活登记是纯进程内存、按会话(scope)隔离,重启即清空——无所谓,模型需要时会重新激活。 +- `activate_skill` 接受当轮目录名单内的 Skill 名称。 +- 同一会话内重复激活同一 Skill:正文内容未变时只返回“已激活”简短文本,不重复注入;部署者改了 `SKILL.md`,下一轮扫描指纹变化,再激活会注入新正文。 +- 激活状态按会话分别记录在进程内存中。重启后,模型在需要时重新激活。 - 激活是纯上下文注入:Skill 指令从属于机器人规则、当前人格与用户的明确请求,不能新增工具或改变权限。 ### 3.4 敏感词扫描对 Skill 的影响 -Skill 相关的全部模型可见产出与 `search_web` 等外部工具走同一敏感词扫描接缝(详见 [docs/admin/sensitive-filter.md](../admin/sensitive-filter.md))。对 Skill 的两层影响: +Skill 工具结果会经过与其他外部工具相同的敏感词扫描(详见 [docs/admin/sensitive-filter.md](../admin/sensitive-filter.md))。对 Skill 的两层影响: -- **描述静态拦截**:description 会进入系统提示静态段(对全群可见),命中拦截词的 Skill 会被整只从清单剔除并记告警日志(`跳过 skill [description-blocked]`),既不出现在系统提示里,也无法激活。 +- **描述静态拦截**:description 会进入系统提示静态段(供模型选择 Skill),命中拦截词的 Skill 会从清单剔除并记告警日志(`跳过 skill [description-blocked]`),既不出现在系统提示里,也无法激活。 - **激活正文预扫**:激活时注入的正文若命中拦截词,会被输出管道整段替换;此时系统不留激活登记,部署者修正 `SKILL.md` 措辞后模型重试即可拿到新正文。 写 Skill 时避开拦截词表里的词汇,是最省事的预处理。 @@ -188,20 +198,20 @@ Skill 相关的全部模型可见产出与 `search_web` 等外部工具走同一 ## 4. 实战一:从零写一个无脚本 Skill -目标:写一个"本群梗百科"——群友问"某个梗/绰号是什么意思"时,AI 查内置词条作答。全程只需要编辑 Markdown 文件。以下示例中的群友昵称、梗出处均为虚构占位,请替换成你自己群里的内容(注意不要写入真实 QQ 号等隐私信息)。 +目标:写一个“本群梗百科”——群友问“某个梗/绰号是什么意思”时,AI 查内置词条作答。全程只需要编辑 Markdown 文件。以下示例中的群友昵称、梗出处均为虚构占位,请替换成你自己群里的内容;公开分享示例时使用合成昵称与标识。 ### 4.1 第一步:想清楚触发条件 -`description` 是 AI 决定何时激活的唯一依据。动笔前先回答两个问题: +`description` 帮助 AI 判断 Skill 适合哪些请求。动笔前先回答两个问题: -1. **什么请求该触发它?**——"XX 是什么梗""某某绰号指谁""这个名场面哪来的"。 +1. **什么请求该触发它?**——“XX 是什么梗”“某某绰号指谁”“这个名场面哪来的”。 2. **回答纪律是什么?**——以词条为准,查不到就如实说没收录。 -把这两个答案写进 description,激活命中率会高很多。 +把这两个答案写进 description,帮助 AI 判断何时使用。 ### 4.2 第二步:建目录 -在项目根执行: +在 §3.1 确定的目录执行;Windows 在已有 `skills` 文件夹中依次新建 `group-meme-pedia`、`references` 文件夹: ```bash mkdir -p skills/group-meme-pedia/references @@ -211,7 +221,7 @@ mkdir -p skills/group-meme-pedia/references ### 4.3 第三步:写 SKILL.md -创建 `skills/group-meme-pedia/SKILL.md`,完整内容如下(可直接复制后修改): +用纯文本编辑器(如记事本或 VS Code)创建 `skills/group-meme-pedia/SKILL.md`。复制下面代码块内部的完整内容,文件第一行是 `---`;网页上的代码块标记无需复制。Windows 记事本“另存为”时选择“所有文件”、UTF-8 编码,并在文件管理器开启“文件扩展名”,确认文件名为 `SKILL.md`,避免保存成 `SKILL.md.txt`。后面的两个参考文件用同样方式保存。 ```markdown --- @@ -226,8 +236,8 @@ description: 本群梗百科:群内黑话、绰号、名场面与出处。当 ## 工作方式 1. 判断问题类型:词语与梗查 references/memes.md,人物绰号查 references/people.md。 -2. 文件不大时直接用 read_skill_resource(skill="group-meme-pedia")整读; - 记不准在哪时先用 search_skill_resources 按关键词定位。 +2. 文件不大时,用 read_skill_resource 读取上一步选定的包内路径; + 记不准在哪时先用 search_skill_resources 按关键词定位。两者的 skill 均为 group-meme-pedia。 3. 词条间有"参见"引用时,继续查被引用的文件。 4. 转述时带上词条"起源"字段里的首次出现时间,让新群友也能看懂。 5. 词条未收录的梗,如实回复"百科还没收录",可请群友找管理员补充词条。 @@ -237,7 +247,7 @@ description: 本群梗百科:群内黑话、绰号、名场面与出处。当 玩梗以词条记载为准,不对词条之外的真人真事做调侃。 ``` -要点:frontmatter 两个必填字段一个都不能少;正文写给 AI 看,用编号步骤交代工作流程;长篇材料全部外置到 `references/`,`SKILL.md` 只留路由和纪律——正文在激活时会整体进入上下文,保持精炼就是控制成本。 +要点:frontmatter 填写 `name` 和 `description`;正文写给 AI 看,用编号步骤交代工作流程;长篇材料全部外置到 `references/`,`SKILL.md` 写清适用请求和工作步骤。正文在激活时会整体进入上下文,将长篇参考材料外置可以减少输入。 ### 4.4 第四步:放参考资料 @@ -272,23 +282,20 @@ description: 本群梗百科:群内黑话、绰号、名场面与出处。当 ### 4.5 第五步:部署与验证 -文件放好后即为部署完成,无需重启。验证两件事: +文件放好并完成 §3.2 的启用设置后,验证以下两项: -```bash -# 1. 目录结构(在项目根执行) -ls -R skills/group-meme-pedia -# skills/group-meme-pedia: -# references SKILL.md -# skills/group-meme-pedia/references: -# memes.md people.md -``` +1. 在 §3.1 确定的目录查看结构(Windows 可直接在文件管理器查看): + + ```bash + ls -R skills/group-meme-pedia + ``` + + 应包含 `SKILL.md`、`references/memes.md` 和 `references/people.md`。 -2. 群里发送 `/skill list`,应看到: +2. 群里发送 `/skill list`,应看到 `group-meme-pedia`。原目录为空时,按前面的单包路径安装后此时共两项;全装官方包后则共三项。已有其他 Skill 时总数相应增加。下面只节选新条目: ```text -已安装 Skill(1): - group-meme-pedia:本群梗百科:群内黑话、绰号、名场面与出处。…… -当前会话已激活:(无) ``` 清单里没有它,就对照 §2.2 逐项检查(最常见:frontmatter 的 `name` 与目录名写得不一致),并看日志里的 `跳过无效 skill` 告警。 @@ -298,22 +305,22 @@ ls -R skills/group-meme-pedia 在群里这样问(措辞贴近 description 的触发条件即可): ```text -@bot 群里说的"红温"是什么梗? +/ai 群里说的“红温”是什么梗? ``` -预期过程:AI 匹配描述 → 激活 `group-meme-pedia`(正文与文件清单注入)→ 检索"红温"命中 `references/memes.md:3` → 读词条 → 按正文纪律带起源时间作答。激活是模型语义判断,问法太绕可能不触发,把 description 的触发条件写具体就是提高命中率的手段。 +也可以在 QQ 输入框中选择机器人形成真实艾特,再输入问题。预期回复会引用梗词条,说明“红温”的含义和首次出现时间(2026-03)。用 `/skill list` 确认当前会话已激活 `group-meme-pedia`。AI 根据请求含义决定是否激活;测试时可使用 description 中列出的典型问法。 --- ## 5. 实战二:写一个带脚本的 Skill -前半篇的 Skill 只有静态资料;想让 AI 拿到**实时数据**(宿主机指标、外部状态),就给它配 `scripts/` 脚本。这半篇面向开发者,以官方 `host-healthcheck` 为例拆解脚本契约与沙箱。 +想让 AI 拿到实时数据,就给 Skill 配 `scripts/` 脚本。本节先安装和运行完整官方 `host-healthcheck`,再拆解其契约,最后写一个只依赖 Python 标准库的小例子。 ### 5.1 脚本契约 -`run_skill_script` 的执行形态(`src/quickquip/llm/skills/tools/run_script.py`): +模型通过 `run_skill_script` 调用脚本,按以下契约交换数据: -- **输入**:模型传入的 `args` 字符串数组,逐字传给脚本(不经 shell 解释),不能含 NUL 字节。运行器只为 stdout/stderr 建立管道,没有为 stdin 建立输入通道——脚本不要指望从标准输入读到模型数据,模型侧的一切输入只有 `args`。 +- **输入**:模型传入的 `args` 字符串数组,逐字传给脚本(不经 shell 解释),不能含 NUL 字节。模型输入通过 `args` 传入,脚本从命令行参数读取;stdout 和 stderr 用于返回结果。 - **输出**:stdout 是给模型看的主通道;stderr 同样会被收集展示,适合放人类可读的告警。两者各受 `script_max_output_bytes`(默认 65536 字节)上限约束,超限即终止脚本并截断。 - **退出码**:0 = 成功;非 0 或超时按错误处理。工具结果带固定包装: @@ -330,16 +337,18 @@ stderr: (empty) - **解释器**:按扩展名映射——`.py` 用 `python3`(PATH 上找不到时回退当前解释器),`.sh` 用 `sh`;不依赖 shebang 与执行位。主机 PATH 上没有 `sh` 时 `.sh` 直接拒绝执行,Windows 主机请写 `.py`。 -### 5.2 沙箱约束 +其中 `stdout` 是 `print` 等语句写出的正常结果,`stderr` 是错误或告警通道;退出码 `0` 表示程序正常完成。`argv` 是脚本收到的命令行参数列表,`cwd` 是执行时所在文件夹。在 Skill 工具中,`cwd` 固定为当前 Skill 的根目录。 + +### 5.2 执行约束 -脚本在部署主机上真实执行,运行器加上了一组结构性约束: +脚本在部署主机上真实执行,运行器加上了以下执行限制。脚本沿用机器人进程的主机权限,部署前由部署者审阅: | 约束 | 内容 | |------|------| | 无 shell | 脚本经结构化 argv 直接启动,参数逐字传递,没有解释层 | -| 最小环境 | 子进程环境白名单仅 `PATH` / `LANG` / `TZ`,bot 进程其余环境变量(含 `.env` 凭证)一律不继承——脚本读不到自定义环境变量,配置要么走 `args`,要么写在脚本默认值里 | +| 最小环境 | 子进程环境白名单仅 `PATH` / `LANG` / `TZ`,需要配置时,通过 `args` 传入或使用脚本内默认值;Bot 的其他环境变量(含 `.env` 凭证)由运行器过滤 | | 固定工作目录 | cwd 固定为该 Skill 目录,脚本内访问文件以该目录为基准 | -| 墙钟超时 | 默认 `script_timeout_ms`(30000ms),单次调用可指定 `timeout_ms`,硬上限 120000ms;超时按进程组 SIGKILL 整组清理 | +| 墙钟超时 | 默认 `script_timeout_ms`(30000ms),单次调用可指定 `timeout_ms`,硬上限 120000ms;超时终止脚本,POSIX 系统同时清理其进程组,其他平台终止直接子进程 | | 执行前复验 | 执行前对脚本做 SHA-256 快照比对,目录扫描之后内容有变化即拒绝执行(防扫描与执行之间被替换) | | 路径限制 | 只能执行 `scripts/` 下的常规文件;`..` 穿越、绝对路径、符号链接逃逸一律拒绝 | @@ -349,7 +358,29 @@ stderr: (empty) `skills.example/host-healthcheck/` 只有两个文件:`SKILL.md` 和 `scripts/collect.py`。 -**SKILL.md 侧**(摘自正文"工作方式"): +先复制完整目录,已全装官方包的读者可跳过此步。在 §3.1 确定的目录执行: + +```bash +cp -r skills.example/host-healthcheck skills/ +``` + +Windows 用文件管理器复制整个 `host-healthcheck` 文件夹。随后在能看到目标 `skills/` 的目录,用已有 Python 环境直接执行完整脚本: + +```bash +python skills/host-healthcheck/scripts/collect.py +``` + +Windows 源码虚拟环境可将 `python` 换为 `.venv/Scripts/python.exe`。自定义 Skill 目录时替换脚本路径。容器部署在实际 Compose 所在目录执行下方命令;若部署时使用了 `-f` 指定文件,沿用相同选项: + +```bash +docker compose exec quickquip python /app/skills/host-healthcheck/scripts/collect.py +``` + +预期输出是一个 JSON 文档,其中 `groups` 列出负载、内存、磁盘等指标;平台不支持的指标会标为 `unavailable`。 + +在群里发送 `/skill list` 确认安装,再发送 `/ai 请用 host-healthcheck 检查机器人运行环境的内存和磁盘`。应收到关键指标摘要及来源说明,已激活项中出现 `host-healthcheck`。模型执行时工具输入为 `{"skill":"host-healthcheck","path":"scripts/collect.py"}`,无需 `args`。直接执行脚本的结果来自当前终端所在环境,群里执行的结果来自 Bot 运行环境。 + +**SKILL.md 侧**(摘自正文“工作方式”): ```markdown 1. 用 `run_skill_script` 执行 `scripts/collect.py`,无需 `args`。 @@ -357,9 +388,9 @@ stderr: (empty) 3. stdout 是单个 JSON 文档,解析后按本手册转述;不要把整段 JSON 原样贴给用户。 ``` -SKILL.md 承担"输出契约 + 转述纪律":告诉模型输出长什么样(顶层字段、指标组、来源视图)、哪些数字该怎么解读、什么必须如实说明。模型只做转述员,脚本只做采集器,职责干净分开。 +SKILL.md 说明输出字段、指标含义和转述要求。脚本采集数据,模型根据这些说明向用户解释结果。 -**collect.py 侧**(`skills.example/host-healthcheck/scripts/collect.py`)是纯 Python 3 标准库的只读探测,一秒内完成。主函数: +**collect.py 侧**(`skills.example/host-healthcheck/scripts/collect.py`)使用 Python 3 标准库进行只读探测。以下主函数与后面的采集函数都是原理片段;完整可执行文件来自刚复制的官方包: ```python def main() -> int: @@ -369,10 +400,10 @@ def main() -> int: return 0 ``` -四个值得学的决定: +这个示例采用四项设计: -1. **stdout 输出单个 JSON 文档**:结构化数据模型解析可靠、体积可控(远低于 64KiB 上限),比自然语言文本稳。 -2. **退出码恒为 0**:文件缺失、平台不适配等"数据拿不到"的情况,编码成对应指标组的 `status: "unavailable"`,让模型照常读取并如实转述;脚本自身只在程序性错误时才非 0 退出。每个采集函数都是这个模式: +1. **stdout 输出单个 JSON 文档**:使用固定字段组织指标与状态,便于模型解析。 +2. **用输出状态表示数据缺失**:文件缺失、平台不适配等“数据拿不到”的情况,编码成对应指标组的 `status: "unavailable"`,让模型照常读取并如实转述;脚本自身只在程序性错误时才非 0 退出。例如负载采集在读取失败时返回不可用状态: ```python def _collect_load(proc_root: Path) -> dict: @@ -383,10 +414,10 @@ def _collect_load(proc_root: Path) -> dict: ... ``` -3. **只用标准库**(`json` / `os` / `shutil` / `pathlib` …):部署环境不保证第三方包,可移植性靠零依赖达成。 -4. **快进快出**:全部探测是几次文件读取,秒级完成,离默认 30s 超时很远;也不起子进程、不写任何文件。 +3. **使用标准库**(`json` / `os` / `shutil` / `pathlib` 等):部署者可以直接运行,省去额外安装。 +4. **读取后及时退出**:通过文件读取收集指标,省去子进程启动与文件写入。 -另外注意它对沙箱的适配:`resolve_config` 从环境变量读探测根的覆盖项(`QQ_HC_*`),但沙箱白名单只放行 `PATH` / `LANG` / `TZ`,所以实际运行永远走脚本内默认值(`/proc`、`/sys/fs/cgroup` 等)——默认值即生产值的设计让同一份脚本在沙箱内外行为一致。 +在 Skill 运行器中,`resolve_config` 从环境变量读探测根的覆盖项(`QQ_HC_*`),而环境变量白名单只放行 `PATH` / `LANG` / `TZ`,因此通过 Skill 工具执行时采用脚本默认探测路径(`/proc`、`/sys/fs/cgroup` 等)。直接运行脚本时,可以用 `QQ_HC_*` 覆盖路径;结果对应所选路径的主机或容器视图。 ### 5.4 写脚本 Skill 的检查清单 @@ -396,37 +427,83 @@ def _collect_load(proc_root: Path) -> dict: 2. 只读、无副作用、可重复执行。 3. 快:目标秒级完成,给 `script_timeout_ms` 留一个数量级的余量。 4. stdout 输出紧凑的机器可读文本(推荐 JSON),总量控制在 64KiB 内。 -5. "数据缺失"编码进输出内容,退出码 0 只留给程序性成功;崩溃信息走 stderr。 -6. 不依赖沙箱外的环境变量;需要参数就约定 `args` 并写进 SKILL.md。 +5. “数据缺失”编码进输出内容,退出码 0 只留给程序性成功;崩溃信息走 stderr。 +6. 需要参数时约定 `args`,并在 SKILL.md 写清字段与默认值。 7. 路径以 Skill 目录(cwd)为基准计算。 8. SKILL.md 里写清输出契约(字段语义)与转述纪律(哪些必须如实说明)。 +### 5.5 写一个完整的日期脚本 Skill + +这个小例子让 AI 读取 Bot 运行环境的当天日期。按 §4 的文件保存方法,在目标 `skills/` 下创建 `calendar-today/scripts/`,再保存以下两个完整文件。 + +`skills/calendar-today/SKILL.md`: + +```markdown +--- +name: calendar-today +description: 当用户询问机器人运行环境今天的日期或星期几时使用,执行自带日期脚本后回答。 +--- + +# 今天的日期 + +1. 用 run_skill_script 执行 scripts/today.py,无需 args。 +2. 输出为 JSON,date 是运行环境的本地日期,weekday 是中文星期名。 +3. 按结果回答日期和星期,说明采用机器人运行环境的本地时间。 +``` + +`skills/calendar-today/scripts/today.py`: + +```python +import json +from datetime import date + + +def main() -> None: + today = date.today() + weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] + print(json.dumps({"date": today.isoformat(), "weekday": weekdays[today.weekday()]}, ensure_ascii=False)) + + +if __name__ == "__main__": + main() +``` + +在能看到目标 `skills/` 的目录执行 `python skills/calendar-today/scripts/today.py`,应输出当天日期和星期的 JSON;Windows 和容器执行方式沿用 §5.3,替换对应脚本路径。然后发送 `/skill list` 确认 `calendar-today` 已安装,再问 `/ai 请用 calendar-today 告诉我机器人那里今天是几号、星期几`,核对回复及已激活项。此处的小脚本可完整复制,后续修改脚本时同步更新 `SKILL.md` 的输入与输出说明。 + --- ## 6. 常见问题与排障 | 症状 | 常见原因 | 处置 | |------|---------|------| -| `/skill list` 里没有新 Skill | name 校验失败(正则、超 64 字符、与目录名不一致);description 缺失或超 1024 字符;缺 frontmatter 围栏;文件超 256KiB;非 UTF-8;SKILL.md 是符号链接 | 对照 §2.2 逐项检查;日志里搜 `跳过无效 skill`,告警带具体原因 | -| 描述没问题,AI 从不激活 | description 没写清触发条件;或命中敏感词被整只剔除(日志 `[description-blocked]`) | 把触发条件写具体("当用户询问……时使用");对照敏感词表改措辞 | +| `/skill list` 里没有新 Skill | 文件放到了其他机器或目录;误保存成 `SKILL.md.txt`;name 与目录名不一致等校验失败 | 先按 §3.1 确认运行目录与文件名,再对照 §2.2;日志里搜 `跳过无效 skill`,告警带具体原因 | +| 复制提问后机器人没有回复 | 使用了普通文字艾特;AI 前缀已自定义;基础 AI 对话尚未可用 | 使用实际配置的前缀,或在 QQ 中选择机器人形成真实艾特;先确认普通 AI 对话能回复 | +| 描述没问题,AI 从不激活 | 工具调用或 Skill 开关关闭;严格工具名单没有 Skill 工具;description 没写清触发条件;或描述命中敏感词被剔除(日志 `[description-blocked]`) | 检查 `settings.runtime.tool_calling_enabled`、`settings.skills.enabled` 与工具名单,保存应用;再核对触发条件及敏感词措辞 | | 激活了但正文没出现 | 注入正文命中拦截词,被输出管道整段替换 | 修正 SKILL.md 措辞;系统未留登记,直接重试即可 | -| 脚本被终止:"脚本运行超过 N ms" | 超过超时上限(默认 30s,硬上限 120s) | 精简脚本耗时;必要时调大 `script_timeout_ms` | -| 脚本结果带"[输出超过 N 字节上限,已截断]" | stdout 或 stderr 超过 65536 字节 | 输出汇总数字,避免全量明细;必要时分多次运行 | -| 脚本拒绝执行:"内容在目录扫描后已变化" | 扫描之后改过脚本文件,SHA-256 复验失败 | 等下一轮请求重新扫描后再试 | -| 检索报错"正则形态不被允许" | 查询含嵌套量词或交叠分支的量化组等病态形态(防灾难性回溯的静态检查) | 改用字面搜索(`is_regex=false`)或改写正则 | -| 系统提示注明"因目录预算超限未列出" | Skill 总量超过 min(上下文窗口 2%, `catalog_max_bytes`) | 精简各 description、减少 Skill 数量,或调大 `catalog_max_bytes` | -| 读取报错"不是有效 UTF-8 文本" | 该文件是二进制 | 二进制资产放 `assets/`(激活时的资源清单会披露),文本资料放 `references/` | +| 脚本被终止:“脚本运行超过 N ms” | 超过超时上限(默认 30s,硬上限 120s) | 精简脚本耗时;必要时调大 `script_timeout_ms` | +| 脚本结果带“[输出超过 N 字节上限,已截断]” | stdout 或 stderr 超过 65536 字节 | 输出汇总数字,避免全量明细;必要时分多次运行 | +| 脚本拒绝执行:“内容在目录扫描后已变化” | 扫描之后改过脚本文件,SHA-256 复验失败 | 等下一轮请求重新扫描后再试 | +| 检索报错“正则形态不被允许” | 查询含嵌套量词或交叠分支的量化组等病态形态(防灾难性回溯的静态检查) | 改用字面搜索(`is_regex=false`)或改写正则 | +| 系统提示注明“因目录预算超限未列出” | Skill 总量超过 min(上下文窗口 2%, `catalog_max_bytes`) | 精简各 description、减少 Skill 数量,或调大 `catalog_max_bytes` | +| 读取报错“不是有效 UTF-8 文本” | 该文件是二进制 | 二进制资产放 `assets/`(激活时的资源清单会披露),文本资料放 `references/` | | 资源清单不完整 | 单 Skill 文件数超过 200 条上限,超出部分不编入清单 | 精简文件数量或合并资料 | -通用排查入口:`/skill list`(装载面)、日志 WARNING(`跳过无效 skill` / `description-blocked` / `skill catalog 超过 … 字节预算`)、`config/llm.toml` 的 `[skills]` 段(上限面)。 +排查时先用 `/skill list` 查看装载结果,再检查日志中的 WARNING。需要调整上限时,在 LLM 配置工作台编辑 `settings.skills`,保存草稿后应用。 --- ## 7. 延伸阅读 +| 实现职责 | 源码入口 | +|---|---| +| 元数据校验、目录扫描与预算 | `src/quickquip/llm/skills/parser.py`、`catalog.py` | +| 激活、资源读取与脚本执行 | `src/quickquip/llm/skills/tools/` | +| 工具注册、每轮清单与启用条件 | `src/quickquip/llm/service_parts/skills.py` | +| `/skill` 命令 | `src/quickquip/adapters/nonebot/command_parts/skills.py` | + - [docs/admin/skills.md](../admin/skills.md):Skill 系统的部署方式与安全模型(部署者视角的权威文档)。 - [docs/dev/llm-module.md](llm-module.md):LLM 模块运行时架构(工具注册、系统提示构建、工具调用循环)。 -- `config/llm.toml.example` 的 `[skills]` 段:全部配置键与默认值的带注释参考。 +- [配置参考](../admin/configuration.md) 的 `settings.skills` 表与 metadata 接口:配置键、默认值、类型及范围。 - `skills.example/`:两个官方 Skill——`self-docs`(无脚本、纯 references 路由检索的范本)与 `host-healthcheck`(带脚本、输出契约与转述纪律的范本)。 --- @@ -435,4 +512,4 @@ def _collect_load(proc_root: Path) -> dict: > > - 本文档基于 QuickQuip 项目编写,规范与数值以 `src/quickquip/llm/skills/` 现行实现为准 > - 示例中的群友昵称、梗出处均为虚构占位 -> - 最后更新:2026-09-19 +> - 最后核对:2026-10-14 diff --git a/skills.example/self-docs/references/docs-dev-sts-formula.md b/skills.example/self-docs/references/docs-dev-sts-formula.md index 634a8782..244b34b1 100644 --- a/skills.example/self-docs/references/docs-dev-sts-formula.md +++ b/skills.example/self-docs/references/docs-dev-sts-formula.md @@ -2,35 +2,35 @@ # STS 公式化回复模块 +本文面向维护尖塔梗功能的开发者,说明词表来源、两种公式的触发方式与扩展入口。群友使用命令时可查[群内指令速查](../user/group-commands.md#3-玩梗转写)。 + ## 1. 模块定位 -`quickquip.sts` 是承载《杀戮尖塔》(Slay the Spire)相关“公式化”梗能力的**独立顶层域**。它与规则引擎(`chat/`)和 LLM 运行时(`llm/`)平行,按“每个公式一个子包”的方式组织,互不耦合,方便后续追加策略不同的新公式。 +`quickquip.sts` 存放《杀戮尖塔》(Slay the Spire)梗的词表、提示词、输出解析与被动触发逻辑。每个公式占一个子包,LLM 调用由 LLM 服务编排,消息与命令入口由应用管线和适配层接入。 当前公式: - **“xxx了”**(`formulas/card_le/`)——把卡牌/遗物名当事件用,加“了”输出。 -- **“故障化”**(`formulas/defectify/`)——`/defectify` 命令,把输入转写成读音贴近「故障机器人」(STS 初始角色 Defect 的官方中文名)的五字梗。 - -“我说xxxx”“假如xxxx”等以后以兄弟子包形式加入。 +- **“故障化”**(`formulas/defectify/`)——`/defectify` 命令,把输入转写成读音贴近“故障机器人”(STS 初始角色 Defect 的官方中文名)的五字梗。 --- -## 2. 词表(地基) +## 2. 词表来源与更新 -公式的前提是一份有时效性的卡牌/遗物中文名表。 +卡牌与遗物中文名存放在随包分发的词表中,生成元信息记录来源快照。 -- **数据源**:[`nkhoit/spire-archive`](https://github.com/nkhoit/spire-archive)。两代游戏的 cards/relics 数据 + 简中本地化,从游戏文件解析(非手抄),覆盖 STS1(361 卡 / 181 遗物)与 STS2(577 卡 / 289 遗物,EA 快照 v0.107.1)。 -- **构建**:`scripts/refresh_sts_lexicon.py` 把两代数据按 ID join 简中、按中文名跨代去重,输出 `src/quickquip/sts/sts_lexicon.json`(1117 条,带来源 SHA / 版本元信息)。刷新时核对 spire-archive 最新 commit、改脚本里的 `SOURCE_SHA` 重跑即可。 -- **加载**:`lexicon.py` 经 `importlib.resources` 读取 vendored JSON,套用 `config.EXCLUDED_NAMES` 得到活跃集合 `NAMES`。vendored 文件保持完整(与上游一致),排除项集中、可审计、刷新不回退。 +- **数据源**:[`nkhoit/spire-archive`](https://github.com/nkhoit/spire-archive)。两代游戏的卡牌、遗物数据与简中本地化,覆盖 STS1(361 卡 / 181 遗物)与 STS2(577 卡 / 289 遗物,EA 快照 v0.107.1)。 +- **构建**:`scripts/refresh_sts_lexicon.py` 按 ID 关联两代条目与简中名称,再按中文名跨代去重,输出 `src/quickquip/sts/sts_lexicon.json`(1117 条,来源提交 `14e05c09dc38`,日期 2026-07-23)。刷新时在项目根运行 `python scripts/refresh_sts_lexicon.py`;先核对要采用的上游提交,并更新脚本的 `SOURCE_SHA` 和 `SOURCE_DATE`,生成后检查词条与元信息。 +- **加载**:`lexicon.py` 经 `importlib.resources` 读取 vendored JSON,套用 `config.EXCLUDED_NAMES` 得到活跃集合 `NAMES`。词表文件保留上游去重结果,排除项单独维护,刷新词表时继续生效。 - **排除标准打防牌**:每个角色的初始 Strike/Defend 跨代去重后坍缩为“打击”“防御”两个 2 字裸词,歧义过大(群聊里几乎不会是玩梗),故排除;含该子串的“完美打击”“究极防御”等不受影响。新增歧义词只需追加到 `EXCLUDED_NAMES`。 -> 词表文件平铺在包根(`sts/sts_lexicon.json`),不放在 `data/` 子目录——根 `.gitignore` 的 `data/` 规则会忽略任意层级的 `data` 目录。 +词表作为包资源存放在 `src/quickquip/sts/sts_lexicon.json`,随安装与打包分发。 --- ## 3. “xxx了”的两条触发路径 -两条路径共用 `prompting.py`(system prompt 注入完整活跃词表作为闭集约束、利于 prompt 缓存)与 `parsing.py`(从模型输出提取并校验合法名,保证 bot 永不发出虚构名字)。 +两条路径共用 `prompting.py`(在系统提示中提供完整活跃词表)与 `parsing.py`(从模型输出提取合法名字,校验通过后才发送)。 ### 3.1 被动路径(`passive.py`) @@ -40,30 +40,30 @@ 2. X 是合法卡牌/遗物名(在活跃词表里)→ **静默**(别人已在玩梗,无需插话); 3. X 不是合法名 → LLM 从词表里挑语义/字面最近的真名 Y → 回复“Y了”。 -反直觉点是“命中真名反而闭嘴、没命中才接话”——喜剧来自把非卡词强行映射进卡牌语义空间。 +梗的效果来自把日常词语映射成卡牌名;群友已经说出合法卡牌名时,机器人保持安静。 - LLM 调用经 `LLMService.generate_card_le_nearest`(provider 解析 + 输出敏感词扫描,输出经 `extract_card_le_name` 校验)。 -- **限频**:`sts_card_le` 桶,按群分桶、强限频,保持“偶发荒诞乱入”而非刷屏。 -- **缓存**:按捕获词的短期 TTL 缓存(300s),降低同一“X了”的重复 LLM 调用——因为 LLM 调用发生在 `resolve_reply` 内、早于框架层的限频判定,缓存能把被限频情形的成本压低(同 `chat/context_rules.py` 的 judge 缓存思路)。 +- **触发控制**:`sts_card_le` 按群限频。调用 LLM 前先检查群规则开关、剩余配额与触发概率;回复送出时再记录配额。 +- **缓存**:捕获词与生成结果缓存 300 秒,跨群复用。相同词语再次通过触发检查时,可以直接使用缓存,减少 LLM 请求。 -接入点:`app/message_pipeline.py` 的 `resolve_reply()` 规则链,位于 `timezone` 之后、规则链末尾(按符号定位:`resolve_reply` 中的 `match_card_le` block,代码注释明写不得抢占时区等具体规则),复用 `rule_switch`(按群开关)与框架的 `rate_limit`。 +接入点在 `app/message_pipeline.py` 的 `resolve_reply()` 规则链末尾、时区猜测之后,复用群级规则开关与限流器。 ### 3.2 主动路径(`/turmfluch` 命令) 显式命令(`turmfluch` = 德语 Turm 尖塔 + Fluch 诅咒),与 `/defectify` 同构: -- 吃跟随文字 / 命令内图片 / 引用消息(`command_parts/sts.py`); -- `LLMService.generate_turmfluch_reply` 把内容喂给 LLM,从词表闭集里选一个最贴切的名字,输出“名了”,经 `extract_card_le_name` 校验 + 输入/输出敏感词扫描; +- 接受跟随文字、命令内图片或引用消息(`command_parts/sts.py`); +- `LLMService.generate_turmfluch_reply` 把内容交给 LLM,从词表闭集里选一个最贴切的名字,输出“名了”,经过输入/输出敏感词扫描,并用 `extract_card_le_name` 校验卡牌名; - `sts_turmfluch` 限频桶(global scope,保护 LLM 用量)。 --- -## 4. 「故障化」公式(`/defectify` 命令) +## 4. “故障化”公式(`/defectify` 命令) 只有主动路径,无被动触发: -- 「故障机器人」=《杀戮尖塔》初始角色 **Defect** 的官方中文名。公式把输入转写成读音依次贴近「故·障·机·器·人」的五字,附一行笑点解析; -- 输入形态与 `/turmfluch` 相同(跟随文字 / 命令内图片 / 引用消息),共用 `llm/single_shot.py` 的 `CommandSingleShotSpec` 管线;差异点只有 prompt(`formulas/defectify/prompting.py`)、解析器(原样透传,无词表闭集校验)、temperature、限频桶与 `log_label`(turmfluch 在 provider 异常路径记日志,defectify 不记); +- “故障机器人”=《杀戮尖塔》初始角色 **Defect** 的官方中文名。公式把输入转写成读音依次贴近“故·障·机·器·人”的五字,附一行笑点解析; +- 输入形态与 `/turmfluch` 相同(跟随文字 / 命令内图片 / 引用消息),共用 `llm/single_shot.py` 的 `CommandSingleShotSpec` 管线。故障化使用自己的提示词,解析器原样接收生成文本,temperature 为 0.9;`turmfluch` 的 temperature 为 0.7,并按词表校验输出。两者使用各自的限频桶,`turmfluch` 还会记录渠道异常日志; - `sts_defectify` 限频桶(global scope,独立于 `llm_chat`,不与 LLM 聊天共享额度); - LLM 编排同 turmfluch:`LLMService.generate_defectify_reply`,prompt 在本域、编排在 `llm/` 域。 @@ -85,11 +85,11 @@ src/quickquip/sts/ └── prompting.py # LLM prompt(音槽谐音梗,无词表) ``` -> 依赖方向说明:STS 公式逻辑(prompt/词表/正则)在 `sts/`,但 LLM 调用编排(provider 解析、敏感词扫描、complete)驻留在 `LLMService` 的 `service_parts/single_shot.py` mixin(`llm/` 域),因此存在 `llm/service_parts/single_shot.py` → `quickquip.sts.*` 的单向导入;`sts/` 本身不反向依赖 `llm/`。命令型入口的重复骨架已在 v1.12.1 收敛为 `llm/single_shot.py` 的 `CommandSingleShotSpec`;若公式进一步增多,再考虑把编排彻底下沉到公式包内。 +`llm/service_parts/single_shot.py` 导入 STS 提示词和解析器,负责渠道选择、敏感词扫描与模型调用。命令型公式通过 `llm/single_shot.py` 的 `CommandSingleShotSpec` 声明输入处理、生成与错误处理策略;STS 公式逻辑保持框架无关。 框架无关的业务逻辑都在 `sts/`;NoneBot 接线在适配层:命令注册在 `adapters/nonebot/command_parts/sts.py`,被动匹配器在 `app/message_pipeline.py`。 -**加新公式**:在 `formulas/` 加一个兄弟子包,自带触发与生成策略,复用 `lexicon` 与 `config` 即可。LLM 调用仍走 `LLMService` 的方法(参照 defectify / turmfluch 的编排位置),不直接伸手进 LLMService 私有成员。当前不为“公式”做抽象注册框架(两个公式的差异点已由 `CommandSingleShotSpec` 承载),等公式进一步增多再视需要抽象。 +**添加公式**:在 `formulas/` 新建子包,编写提示词、解析或触发策略,并按需复用 `lexicon` 与 `config`。参照 `defectify` 和 `turmfluch` 在 LLM 服务中添加公开编排方法,在适配层注册命令;被动公式则接入应用消息管线。命令型公式复用 `CommandSingleShotSpec`。 --- diff --git a/skills.example/self-docs/references/docs-dev-style.md b/skills.example/self-docs/references/docs-dev-style.md index 321a04b3..da15572c 100644 --- a/skills.example/self-docs/references/docs-dev-style.md +++ b/skills.example/self-docs/references/docs-dev-style.md @@ -2,7 +2,7 @@ # QuickQuip 代码规范与架构原则 -本文件定义 QuickQuip 源码应如何组织,使贡献者能够安全地理解、修改和验证它。分层与领域所有权见 [`architecture.md`](architecture.md);分支、评审和验证流程见 [`branching.md`](branching.md)。 +本文件定义 QuickQuip 源码应如何组织,使贡献者能够安全地理解、修改和验证它。分层与领域所有权见 [`architecture.md`](architecture.md);分支、评审和验证流程见 [`branching.md`](branching.md);全仓公开文档的写作、维护和审查规范见 [`documenting.md`](documenting.md)。 ## 工具基线 diff --git a/skills.example/self-docs/references/docs-dev-testing.md b/skills.example/self-docs/references/docs-dev-testing.md index adb8bab3..a3710bd1 100644 --- a/skills.example/self-docs/references/docs-dev-testing.md +++ b/skills.example/self-docs/references/docs-dev-testing.md @@ -2,7 +2,7 @@ # QuickQuip 测试纪律 -**不要为了测试而测试。** 测试应当切中要害、目的明确,保护有价值的行为与可信的回归风险。没有独立保护价值、只会增加维护摩擦的测试应当删除;有价值的场景应当通过有效的准备和断言得到保护。 +测试应有明确目的,保护有价值的行为与可信的回归风险。有价值的场景通过有效的准备和断言得到保护;缺少独立保护价值、增加维护摩擦的测试应当删除。 本文定义测试的准入、编写、审查与退出规范,适用于单元测试、集成测试和其他自动化验证。源码与职责边界见 [`style.md`](style.md);环境搭建见 [`CONTRIBUTING.md`](../../CONTRIBUTING.md);运行命令、变更分级和交付验证见 [`branching.md`](branching.md)。 @@ -18,7 +18,7 @@ 用户可见行为、权限与数据隔离、资金结算、持久化与恢复、协议兼容、取消与外部副作用是有意义的保护对象。优先补足缺失的风险边界。普通文案调整、简单转发和低风险可逆修改不自动要求新增测试;重构仅在相关风险缺乏保护时补充现有行为的验证。 -测试数量、断言数量、覆盖率和代码行数用于发现值得审查的区域,不设增加或删除配额。覆盖到某行只说明代码执行过;能否发现目标错误取决于输入和断言。 +测试数量、断言数量、覆盖率和代码行数用于发现值得审查的区域。评审依据是输入能否触发目标路径、断言能否发现目标错误;增加或删除测试没有数量配额。 ## 断言:精确到契约所需的程度 @@ -33,7 +33,7 @@ | 普通可调默认值 | 验证默认回退、继承和显式覆盖,避免重复维护一份默认值清单。 | | 影响权限、主动发送、费用或数据暴露的默认策略 | 明确保护策略及其后果;必要时精确固定默认值。 | -引用生产常量适合表达“按当前容量限制处理”或“非法配置回退默认”等关系。独立的协议值、结算结果和安全策略仍需独立期望值,避免错误实现与测试期望一起变化后继续通过。期望结果应有独立依据,不用待测算法的复算结果证明该算法正确。 +引用生产常量适合表达“按当前容量限制处理”或“非法配置回退默认”等关系。协议值、结算结果和安全策略需要有独立依据的期望值,防止错误实现与测试期望一起变化。计算期望结果时,从契约推导,使用与待测算法独立的方法。 不要批量把 `==` 换成 `in`,也不要统一降级为非空或类型检查。只有“结果可见”或“返回该类型”本身就是目标契约时,这类断言才足够。格式化成功提示不能单独证明写入、发送或退款成功。 @@ -71,7 +71,7 @@ Mock 用于控制网络、外部服务、时钟等依赖。校验请求映射时 - 门禁:满足质量、批次和会话等其他条件,验证目标开关是否阻止实际操作。 - 失败与部分成功:观察失败后的状态、清理、重试或后续处理;确认失败不能被记录为成功。 - 并发与取消:让任务到达明确的同步点,验证同时在途、停止副作用或释放资源。 -- 脱敏:输入实际包含哨兵值,验证应当保留的信息仍在、敏感值已被消除。 +- 脱敏:输入实际包含哨兵值,验证必要信息得到保留、敏感值已被消除。 调用次数、顺序和对象身份可以承载契约。例如禁止工具重放、只退款一次、事务提交顺序和共享状态实例都可能需要精确验证。内部 helper 的组织方式不自动成为契约。 diff --git a/skills.example/self-docs/references/docs-dev-tool-discovery.md b/skills.example/self-docs/references/docs-dev-tool-discovery.md index 44b82d71..e5c82e2d 100644 --- a/skills.example/self-docs/references/docs-dev-tool-discovery.md +++ b/skills.example/self-docs/references/docs-dev-tool-discovery.md @@ -2,19 +2,19 @@ # LLM 工具发现实现说明 -本文面向开发者,说明 QuickQuip 本地 `tool_search` 机制的实现边界、数据流和测试策略。 +本文面向开发者,说明 QuickQuip 本地工具发现的数据流、启用条件、调用规则和测试入口。 --- ## 1. 设计目标 -QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider,因此工具发现不依赖 Claude 原生 tool search。当前实现复用项目已有工具调用协议,在服务层维护一个本地工具目录: +QuickQuip 在服务层维护本地工具目录,四类协议适配器共用工具发现结果,并按各自的工具调用协议发送请求: - 初始请求只暴露常驻工具 - `tool_search` 根据 query 搜索工具 manifest - `tool_list` 在搜索失败时列出工具组、名称、摘要,或按精确名称加载工具 - 工具循环把匹配到的真实工具加入下一轮 `LLMRequest.tools` -- provider 适配层继续按原来的 tools/function calling 协议序列化 +- provider 适配层按对应协议序列化工具定义与调用 --- @@ -28,7 +28,7 @@ QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider, | `src/quickquip/llm/tool_discovery.py` | 持有单次循环的已加载工具集(`loaded_names`),实现 `tool_search` / `tool_list` 元工具 handler | | `src/quickquip/llm/tool_loop.py` | 工具调用循环本体,把命中或精确加载的工具交给发现状态并带进下一轮请求 | | `src/quickquip/llm/prompting.py` | discovery 模式下生成工具发现提示 | -| `src/quickquip/llm/config.py` | 读取 `[tools]` 下的 discovery 配置 | +| `src/quickquip/llm/config.py`、`config_document.py` | 定义配置对象,校验并编译 `settings.tools` 下的工具发现配置 | --- @@ -56,7 +56,7 @@ QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider, - 支持 `enabled_names` 白名单和 `exclude_names` - `category` 参数可限制搜索范围,例如 `mcp:github` -第一版没有引入 embeddings,避免新增运行时依赖和索引维护成本。 +搜索在现有 manifest 上进行字符串匹配和排序。 `tool_list` 提供目录式兜底: @@ -72,7 +72,7 @@ QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider, `discovery_mode = "off"`: -- 保持旧行为,所有启用工具直接进入 `LLMRequest.tools` +- 所有启用工具直接进入 `LLMRequest.tools` `discovery_mode = "on"`: @@ -82,19 +82,19 @@ QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider, - 统计不在 `always_loaded` 里的可延迟工具数量 - 当可延迟工具数大于 `discovery_min_tools` 时启用 -- 小工具集保持旧行为,避免为少量工具增加额外模型轮次 +- 小工具集直接发送全部启用工具 -如果 `tool_search` 被 `[tools].enabled` 白名单排除,discovery 不会启用。常规配置应同时保留 `tool_search` 和 `tool_list`。 +配置位于 `settings.tools`,由草稿保存与应用流程生效。工具调用总开关 `settings.runtime.tool_calling_enabled` 默认关闭,开启后才向模型提供工具。`settings.tools.enabled` 排除 `tool_search` 时,发现机制关闭;配置严格名单时应同时保留 `tool_search` 和 `tool_list`。 --- ## 6. 边界与限制 -- `tool_search` 和 `tool_list` 只负责发现或加载工具,不直接执行目标工具。 +- `tool_search` 和 `tool_list` 发现或加载工具;模型在后续调用中请求执行目标工具。 - 动态加载只在当前工具调用循环内有效,不写入数据库。 - 模型直接调用未加载工具时,工具循环返回错误提示。 -- `discovery_max_loaded_tools` 限制单轮循环内动态加载总量。 -- `search_web` 仍保留独立搜索 failsafe;工具发现不改变联网搜索工具的调用上限逻辑。 +- 动态添加工具时,用 `discovery_max_loaded_tools` 与当前已加载工具数比较;常驻工具计入,达到上限后停止新增工具,已有集合保留。 +- `search_web` 的调用上限由独立的搜索保护逻辑执行。 --- @@ -109,7 +109,9 @@ QuickQuip 支持 OpenAI / Claude / Gemini / OpenAI Responses 四类 provider, - MCP 工具先被 `tool_search` 找到,再在下一轮动态加载并执行 - `tool_list` 通过 groups/group/load 兜底加载 MCP 工具并执行 - `tests/unit/llm/test_config_env_expand.py` - - `[tools]` discovery 配置读取 + - 旧 TOML 的工具发现配置读取与迁移输入兼容 + +数据库配置校验另见 `tests/unit/llm/test_config_document.py`。 建议修改该功能后至少运行: diff --git a/skills.example/self-docs/references/docs-dev-versioning.md b/skills.example/self-docs/references/docs-dev-versioning.md index 7d68a135..17757a61 100644 --- a/skills.example/self-docs/references/docs-dev-versioning.md +++ b/skills.example/self-docs/references/docs-dev-versioning.md @@ -4,7 +4,7 @@ 本项目采用三段式版本号 `Major.Minor.Patch`,参考 SemVer 的格式,并借鉴 Minecraft 的主题更新与系列内累积更新习惯。版本号用于表达产品阶段、更新主题和迭代归属;兼容性与升级要求以各版本发布说明为准。 -本文是非强制性的发布决策参考,自采用之日起适用。版本号的最终解释权与决策权归维护者本人所有,本文提供常规参考而非约束。Major 版本号默认保持不动,破坏性变更同样默认沿用当前 Major,仅当维护者明确决定时才提升。维护者可以根据实际需要作出例外,并在必要时说明原因。历史版本保留原有编号与发布记录,不追溯调整。分支、验证、评审与发布操作见 [`branching.md`](branching.md)。 +本文是非强制性的发布决策参考,自采用之日起适用。版本号由维护者最终解释与决定,可以按实际需要作出例外,并在必要时说明原因。Major 默认保持不动,破坏性变更同样沿用当前 Major;提升 Major 需要维护者明确决定。历史版本保留原有编号与发布记录。分支、验证、评审与发布操作见 [`branching.md`](branching.md)。 ## 正式版本 @@ -18,7 +18,7 @@ “累积”表示包含同一系列此前版本的更新内容。能否跨多个版本直接升级、是否需要中间迁移,以升级说明为准。发布可以按一批工作完成的节奏进行,无需固定周期。 -正式版本保持递增,提升 Minor 或 Major 时将右侧各段归零。可以为纪念或系列收尾选择特定的 Patch 号并跳号。纪念编号与“稳定收尾”等表述不自动构成长期支持承诺;推荐部署版本、维护期限和修复范围另行说明。 +正式版本保持递增,提升 Minor 或 Major 时将右侧各段归零。可以为纪念或系列收尾选择特定的 Patch 号并跳号。推荐部署版本、维护期限和修复范围在发布说明中另行确定。 ## 兼容性与升级说明 @@ -64,7 +64,7 @@ dev 使用 `<目标正式版本>-dev.N`,例如 `1.15.1-dev.0` 或 `1.16.0-dev. 需要精确复现代码时,同时记录 Git SHA;工作区存在未提交改动时,应明确记录该状态。仅凭相同的 `dev.N` 无法确认代码完全一致。 -版本更新可作为 `chore:` 改动按 Develop direct 流程处理。Commit、push 和其他发布操作仍遵守 [`branching.md`](branching.md) 的授权规则。 +版本更新可作为 `chore:` 改动按 Develop direct 流程处理。Commit、push 和其他发布操作遵守 [`branching.md`](branching.md) 的授权规则。 ## 发布候选与正式冻结 diff --git a/skills.example/self-docs/references/docs-index.md b/skills.example/self-docs/references/docs-index.md index 1863519c..053401df 100644 --- a/skills.example/self-docs/references/docs-index.md +++ b/skills.example/self-docs/references/docs-index.md @@ -29,11 +29,13 @@ QuickQuip 是一个基于 NoneBot2 + OneBot V11 的规则驱动优先 QQ 群聊 |------|------| | [admin/onebot-adapters.md](admin/onebot-adapters.md) | OneBot 适配器状态与选择——协议边界、各适配器部署 profile、准入状态与迁移前验证清单 | | [admin/deployment.md](admin/deployment.md) | 云端部署指南——服务器选型、Docker Compose 编排、OneBot 协议端登录、贴吧登录态、Web Admin 反代、日常维护与排障 | -| [admin/configuration.md](admin/configuration.md) | 完整配置参考——`.env` 环境变量、`llm.toml`、`generation.toml`、`awakening.toml`、`chat_rules.toml`、`games.toml`、`sensitive_words.toml`、`personas/` 所有可配项 | +| [admin/llm-configuration.md](admin/llm-configuration.md) | LLM 数据库配置管理——迁移、草稿保存与应用、JSON 交换、备份恢复及 1.16.1 回退 | +| [admin/client-fingerprints.md](admin/client-fingerprints.md) | 请求指纹 Profile——品牌 UA、安装与纪元身份及模板配置 | +| [admin/configuration.md](admin/configuration.md) | 完整配置参考——`.env` 环境变量、LLM 配置数据库、`generation.toml`、`awakening.toml`、`chat_rules.toml`、`games.toml`、`sensitive_words.toml`、`personas/` 所有可配项 | | [admin/tool-discovery.md](admin/tool-discovery.md) | LLM 工具发现配置——大量 MCP 工具接入时的 `tool_search`、`tool_list`、常驻工具和排障建议 | -| [admin/mcp-servers.md](admin/mcp-servers.md) | MCP Server 接入指南——transport 选择、接入清单、最小 http 示例与排障 | +| [admin/mcp-servers.md](admin/mcp-servers.md) | MCP Server 接入指南——本地 echo 首次练习、transport 选择、接入清单与排障 | | [admin/skills.md](admin/skills.md) | Skill 系统部署与安全模型——`skills/` 目录约定、脚本执行隔离、资源上限与预置 Skill | -| [admin/game-config.md](admin/game-config.md) | 游戏系统管理——游戏开关、参数配置、数据库文件、故障排查 | +| [admin/game-config.md](admin/game-config.md) | 游戏系统管理——结束对局、停用方式、参数配置、数据维护与排障 | | [admin/sensitive-filter.md](admin/sensitive-filter.md) | 敏感词过滤器——词表配置、接入点、日志与测试方法 | | [admin/migration-napcat-to-llbot.md](admin/migration-napcat-to-llbot.md) | NapCat → LLBot 历史迁移记录——当时的风控背景、迁移步骤与回退思路 | | [admin/web-admin.md](admin/web-admin.md) | Web 管理后台——鉴权结构、Session 管理、反向代理配置、日志/Trace/各标签页功能列表 | @@ -45,16 +47,19 @@ QuickQuip 是一个基于 NoneBot2 + OneBot V11 的规则驱动优先 QQ 群聊 | 文件 | 说明 | |------|------| | [docs/dev/README.md](dev/README.md) | 开发文档索引——公共/私有边界、文档职责和维护规则 | +| [docs/dev/documenting.md](dev/documenting.md) | 全仓公开文档规范——读者与职责、事实与证据、文风、操作示例、维护与审查 | | [docs/dev/style.md](dev/style.md) | 代码规范与架构原则——职责边界、禁止的上帝结构、输入/状态/测试契约 | -| [docs/dev/testing.md](dev/testing.md) | 测试纪律——不要为了测试而测试;准入、断言、反模式与删留依据 | +| [docs/dev/testing.md](dev/testing.md) | 测试纪律——保护目标、准入、断言、反模式与删留依据 | | [docs/dev/branching.md](dev/branching.md) | 开发工作流与发布流程——六级变更分级、评审(含 KHPilot Bot Review 机制与双轨交叉核对)、验证、release 与 hotfix | | [docs/dev/versioning.md](dev/versioning.md) | 版本号约定——三段式版本、主题更新系列、dev 批次与发布候选规则 | | [docs/dev/architecture.md](dev/architecture.md) | 项目架构与结构——三层架构、依赖方向、组合根、目录用途与数据边界 | | [docs/dev/record-identities.md](dev/record-identities.md) | 记录正文与成员身份契约——正文片段结构、身份分层缓存与消费口径 | | [docs/dev/game-framework.md](dev/game-framework.md) | 游戏框架开发指南——BaseGame 接口、economy API、Session 模式 vs RPG 模式、扩展新游戏步骤 | +| [docs/dev/llm-configuration-api.md](dev/llm-configuration-api.md) | LLM 配置 API——修订冲突、任务状态、参数 metadata 与前端接口契约 | +| [docs/dev/llm-configuration-frontend.md](dev/llm-configuration-frontend.md) | LLM 配置前端交付——页面任务、保存与应用、字段继承、接口接线和设计验收 | | [docs/dev/llm-module.md](dev/llm-module.md) | LLM 模块详解——触发规则、上下文边界、人格注入设计、配置说明、群内命令、部署注意事项 | -| [docs/dev/mcp-integration.md](dev/mcp-integration.md) | MCP 集成约定——transport 选择、Docker Socket 取舍、推荐架构、现有 MCP server 列表 | -| [docs/dev/regex-tutorial.md](dev/regex-tutorial.md) | 正则表达式教程——从零开始,以项目实际规则为例,覆盖基础语法到进阶特性 | +| [docs/dev/mcp-integration.md](dev/mcp-integration.md) | MCP 集成约定——transport 选择、协议协商、部署职责与工具结果处理 | +| [docs/dev/regex-tutorial.md](dev/regex-tutorial.md) | 正则表达式教程——运行第一个练习、上线一条回复规则,再学习基础语法到进阶特性 | | [docs/dev/skill-tutorial.md](dev/skill-tutorial.md) | Skill 编写教程——从零开始写自己的 Skill:规范速查、安装激活、无脚本与带脚本实战 | | [docs/dev/mcp-tutorial.md](dev/mcp-tutorial.md) | MCP 概念教程——协议概念、QuickQuip 接入模型、四种 transport 实例与配置全解 | | [docs/dev/tool-discovery.md](dev/tool-discovery.md) | LLM 工具发现实现说明——manifest、动态加载循环、模式语义和测试覆盖 | @@ -67,5 +72,5 @@ QuickQuip 是一个基于 NoneBot2 + OneBot V11 的规则驱动优先 QQ 群聊 | 文件 | 说明 | |------|------| | [../README.md](../README.md) | 项目入口——功能亮点与快速开始 | -| [../ROADMAP.md](../ROADMAP.md) | 演进路线——版本锁定 scope 与中长期方向 | +| [../ROADMAP.md](../ROADMAP.md) | 演进路线——候选计划、待调研问题与中长期方向 | | [../CHANGELOG.md](../CHANGELOG.md) | 变更记录——按 Keep a Changelog 规范维护 | diff --git a/skills.example/self-docs/references/docs-user-group-commands.md b/skills.example/self-docs/references/docs-user-group-commands.md index d78af96d..03e25c1c 100644 --- a/skills.example/self-docs/references/docs-user-group-commands.md +++ b/skills.example/self-docs/references/docs-user-group-commands.md @@ -16,12 +16,12 @@ QuickQuip 在群里有两类能力:规则回复(复读、接龙、时区猜 ## 1. 怎么触发 AI -默认两种触发方式:消息以 `/ai` 开头(默认前缀,管理员可修改),或消息里 @机器人,例如 `/ai 你觉得今天适合熬夜吗`、`@机器人 这句话是什么意思`。普通聊天 AI 默认不会乱插话;例外是管理员开启的唤醒模块,会在低频场景下主动搭话(见 §5 唤醒模块)。 +群内 AI 开启后,默认有两种触发方式:消息以 `/ai` 开头(默认前缀,管理员可修改),或消息里 @机器人,例如 `/ai 你觉得今天适合熬夜吗`、`@机器人 这句话是什么意思`。管理员还可以开启唤醒模块,让 AI 在指定的低频场景下主动搭话(见 §5 唤醒模块)。 触发时支持这些输入形态: - 文字直接跟在触发词后面;带图时 AI 会一起看,只发 `/ai` 加一张图则按“请描述这张图片”理解。 -- 引用一条消息再触发:当前提问和被引用内容会分开理解,不会把被引用的人当成提问者;引用合并转发时,会尽量读取其中的正文(转发中的图片不再附带原图,非视觉模型下以文字图注形式进入)。 +- 引用一条消息再触发:当前提问和被引用内容会分开理解,并保留各自的发言人。引用合并转发时会尽量读取正文;转发内的原图不会作为图片附件发送,非视觉模型使用文字图注。 - 语音消息配合 `/ai` 或 @机器人:语音自动转写成文字进入 AI;协议端自带文本转录时优先使用转录。语音同时计入词云和消息统计。 模型在对话中生成的图片(如生图工具的产出)会作为图片消息直接发到群里,附在回复正文之后;发送频率有保护,超限时图片丢弃、正文照常。 @@ -138,7 +138,7 @@ QuickQuip 在群里有两类能力:规则回复(复读、接龙、时区猜 ## 5. 管理员可用命令 -以下命令需要群管理员或群主身份。管理员指 OneBot 协议里的群管理角色,与机器人部署者无关。 +本文中的管理员包括群管理员、群主,以及部署者登记的[全局管理员](../admin/global-admins.md)。以下群聊命令按这些身份授权;部署者需要具有其中一种身份。 ### AI 开关与配置 @@ -152,12 +152,12 @@ QuickQuip 在群里有两类能力:规则回复(复读、接龙、时区猜 | `/llm trigger at on / off` | 开关艾特触发(仅群聊有此命令) | | `/llm memory on / off` | 开关记忆注入 | | `/llm auto_memory on / off / reset / status` | 自动记忆抽取的开关、跟随全局默认、查看 | -| `/llm effort <档位>`(`default` / `clear` 恢复跟随渠道配置) | 本群思考档位覆盖,六档:`low / medium / high / xhigh / max / ultra`;改完回复会给出实际生效档(部分模型词表较窄会自动降档,如 ultra 落到 max)。注意:不配置时模型用自身默认档,GLM、Kimi、MiniMax 等模型的默认档即为最高档,恢复默认等于放弃档位带来的成本控制。切换后若回复出现 400 等 4xx 错误,说明该渠道或模型不支持此档位,用 `default` 恢复即可 | -| `/llm delivery intermediate / final / all ` | 分段交付两域的按群开关:`intermediate` = 中间轮发送(多轮工具回复的过程正文照常发出),`final` = 最终轮分段(最终长回复按自然段拆成多条),`all` = 两域同时操作;`/llm delivery status`(或带域)查看当前值与全局默认,默认两域关闭 | +| `/llm effort <档位>`(`default` / `clear` 恢复跟随渠道配置) | 本群思考档位覆盖,六档:`low / medium / high / xhigh / max / ultra`;改完回复会给出实际生效档(部分模型词表较窄会自动降档,如 ultra 落到 max)。模型默认档由上游决定;希望控制思考成本时,请显式选择适合当前模型的档位。切换后若回复出现 400 等 4xx 错误,说明该渠道或模型不支持此档位,用 `default` 恢复即可 | +| `/llm delivery intermediate / final / all ` | 按群设置回复发送方式:`intermediate` 控制是否发送多轮工具调用中的过程正文,`final` 控制是否把最终长回复按自然段拆成多条,`all` 同时设置这两项;`/llm delivery status`(或带域)查看当前值与全局默认,默认两域关闭 | | `/llm context_limit <条数>`(`reset` 或 `off` 恢复默认) | 把本群上下文改为固定保留最新 n 条(上限 1024;默认由会话纪元自动管理,窗口随对话增长、冷场后收缩);重启和 `/llm clear_context` 不影响此设置 | | `/llm clear_context` | 清空本群短期上下文,AI“串台”或记错上下文时用 | | `/llm delete_msg` | 引用一条消息发送本命令,或 `/llm delete_msg <消息ID>`,从上下文删除该条 | -| `/llm reload` | 重载 LLM 配置并重置本群上下文条数覆盖;作用于整个部署,重载后的探活会发一条真实请求,可能产生计费 | +| `/llm reload` | 重载已发布 LLM 配置并重置本群上下文条数覆盖;作用于整个部署,重载后的探活会发一条真实请求,可能产生计费 | | `/llm probe` | 并发探活所有 provider,逐个汇报可用性 | | `/llm mcp reload` | 拉取 MCP 镜像并重连,完成后返回连接状态 | @@ -182,8 +182,8 @@ AI 聊天自带两个行为开关(默认开启): | 规则名 | 说明 | |------|------| -| `llm_quote_reply` | AI 回复时以引用形式锚定触发它的那条消息 | -| `llm_at_mention` | AI 回复里的「@名字」转成真实艾特并提醒对方;关闭后名字保留为纯文本。同一成员短时间内重复艾特会被降级为纯文本(冷却时长见部署配置 `mention_cooldown_seconds`) | +| `llm_quote_reply` | AI 回复时引用触发它的那条消息 | +| `llm_at_mention` | AI 回复里的“@名字”转成真实艾特并提醒对方;关闭后名字保留为纯文本。同一成员短时间内重复艾特会被降级为纯文本(冷却时长见部署配置 `mention_cooldown_seconds`) | ### 唤醒模块 @@ -198,11 +198,11 @@ AI 聊天自带两个行为开关(默认开启): | `awakening_boredom` | 群聊沉寂达到配置时长后主动冒泡 | | `awakening_fallback` | 按低概率兜底触发 | -行为要点:只有 `awakening_extend` 会承接你刚刚明确艾特或前缀召唤后的后续对话,其余唤醒不会让 bot 持续接普通聊天;兴趣、相关性、答疑、延长和无聊唤醒触发时会携带群内最近发过的少量历史图片,显式触发和兜底唤醒不带。无聊唤醒默认关闭,需要本群单独开启:`/awakening boredom on / off`。具体阈值和兴趣话题由部署者在 `config/awakening.toml` 配置。 +行为要点:只有 `awakening_extend` 会承接你刚刚明确艾特或前缀召唤后的后续对话,其他规则按各自条件独立判断是否触发;兴趣、相关性、答疑、延长和无聊唤醒触发时会携带群内最近发过的少量历史图片,显式触发和兜底唤醒不带。无聊唤醒默认关闭,需要本群单独开启:`/awakening boredom on / off`。具体阈值和兴趣话题由部署者在 `config/awakening.toml` 配置。 ### 每日播报与总结 -播报(早/午/晚报)和总结(日报/周报/月报)默认关闭,开启需要管理员执行命令,且部署者需在 `config/llm.toml` 全局启用对应功能。生成与发布时刻由部署配置的定时任务决定。立即生成类命令每分钟限一次。 +播报(早/午/晚报)和总结(日报/周报/月报)默认关闭,开启需要管理员执行命令,且部署者需在 LLM 配置中全局启用对应功能并保存应用。生成与发布时刻由部署配置的定时任务决定。立即生成类命令每分钟限一次。 | 命令 | 说明 | |------|------| @@ -213,11 +213,13 @@ AI 聊天自带两个行为开关(默认开启): | `/summary weekly / monthly on / off / status`(别名 周报 / 月报) | 开关群周报 / 群月报,与日报相互独立 | | `/summary weekly / monthly now` | 立即生成上一个完整周期(上周 / 上月)的回顾 | -周报、月报与日报的差异:时间跨度更长;三者聊天记录均经压缩序列化后交给 AI(按天分节、同分钟连发合并、复读折叠)。周报与日报吃全量窗口;月报按自然周公平分配字符预算组装输入(高活跃日尽量完整,低活跃时段抽稀),在控制单次输入体量的同时覆盖全周期。播报生成中若当前模型输出异常中断,会自动改用级联里的下一个播报模型,不会发出半截简报。 +日报、周报和月报分别回顾一天、一周和一个月。三者都会按天整理记录、合并同分钟连发并折叠复读,再交给 AI。周报与日报使用整个时间窗口的记录;月报按自然周分配输入预算,先完整保留记录较少的平静日,再为高活跃日分配剩余额度,超出预算时会抽样或略过部分日期。 + +播报生成中若当前模型输出异常中断,会自动改用级联里的下一个播报模型,不会发出半截简报。 ### 定时消息 -定时消息是"到点自动向群里发内容"的任务,由管理员用命令管理,也可以在 Web Admin 的「定时消息」页面维护。任务持久化保存,重启不丢。cron 为 5 段式(分 时 日 月 周),按北京时间(Asia/Shanghai)触发;注意周字段 0=周一、6=周日,不支持用 7 表示周日。 +定时消息是“到点自动向群里发内容”的任务,由管理员用命令管理,也可以在 Web Admin 的“定时消息”页面维护。任务持久化保存,重启不丢。cron 为 5 段式(分 时 日 月 周),按北京时间(Asia/Shanghai)触发;注意周字段 0=周一、6=周日,不支持用 7 表示周日。 | 命令 | 说明 | |------|------| @@ -228,7 +230,7 @@ AI 聊天自带两个行为开关(默认开启): | `/schedule on / off <任务ID>` | 启用 / 停用任务 | | `/schedule del <任务ID>` | 删除任务 | -普通群友也可以直接对 bot 说"今晚七点提醒我看 KPL"之类的话,AI 会自行创建一次性的 LLM 定时任务(需要部署者在 `config/llm.toml` 的 `[tools] enabled` 中启用 `manage_scheduled_messages`)。LLM 类任务受规则开关 `scheduled_message_llm` 控制(`/disable` / `/enable`),且需要本群 LLM 处于启用状态。 +普通群友也可以直接对 bot 说“今晚七点提醒我看 KPL”之类的话,AI 会自行创建一次性的 LLM 定时任务(需要部署者开启工具调用,允许定时消息管理工具,并保存应用配置)。LLM 类任务受规则开关 `scheduled_message_llm` 控制(`/disable` / `/enable`),且需要本群 LLM 处于启用状态。 ### 统计、词云与热重载 @@ -254,17 +256,19 @@ AI 聊天自带两个行为开关(默认开启): ## 6. 记忆与隐私 -AI 的“记忆”分两层:短期上下文是最近几轮对话,可用 `/llm clear_context` 清掉;长期记忆是手动 `/remember` 存入的内容,用 `/memories` 查看、`/forget` 删除。 +AI 的“记忆”分两层:短期上下文是最近几轮对话,可用 `/llm clear_context` 清掉;长期记忆包括手动 `/remember` 存入的内容,以及开启自动记忆抽取后保存的内容,用 `/memories` 查看、`/forget` 删除。 + +被艾特的成员如果有已登记的群内档案,AI 会参考该档案,即使对方近期没有发言。当前提及使用已登记的身份名称,对话历史保留写入时的发言人名称。 -被艾特的成员若在群级身份表登记过档案,即使近期没发言,其档案也会注入当轮上下文供 AI 参考;@提及一律按群级身份表渲染为标准身份。 +AI 的短期上下文由会话纪元自动管理,默认随对话增长、冷场后收缩;管理员可用 `/llm context_limit` 设置固定条数。机器人还会持续保存群聊归档,用于日报、词云、播报和周/月报。归档包含发言人昵称、消息时间与机器人发言,报告会标注机器人身份。 -AI 的对话上下文只取最近若干条,显式触发时才会用到;与对话记忆分开,机器人会为所在群持续记录一份聊天归档(用于生成每日总结、词云、播报与周/月报,含发言人昵称与消息时间,机器人自己的发言同样入档并在报告中标注,不用于对话上下文),该归档长期保留、不随清理命令删除,管理员可在管理后台查询与删除已生成的总结。 +`/llm clear_context` 清理短期对话上下文,群聊归档会继续保留。管理员可在管理后台查询和删除已生成的总结。 --- ## 7. 常见问题 -**发了消息 AI 不理我?** AI 只在 `/ai` 前缀(或管理员改过的前缀)和 @机器人时回复,普通聊天不会触发;确认本群 AI 已开启可看 `/llm status`。 +**发了消息 AI 不理我?** 先用 `/llm status` 确认本群 AI 已开启,再用 `/ai` 前缀(或管理员设置的前缀)或 @机器人 提问。唤醒规则的触发条件见 §5。 **AI 没记住我手动加的记忆?** 先 `/llm memory status` 看“记忆注入”是否开启,再 `/memories` 确认记忆确实存进去了。 diff --git a/skills.example/self-docs/references/docs-user-group-games.md b/skills.example/self-docs/references/docs-user-group-games.md index b3f68b66..935e3ce8 100644 --- a/skills.example/self-docs/references/docs-user-group-games.md +++ b/skills.example/self-docs/references/docs-user-group-games.md @@ -16,7 +16,7 @@ | 牛牛大作战 | `/注册牛牛` — 养牛、击剑、打胶、冲排行 | | 金币系统 | `/签到` `/金币` `/金币排行` | -通用命令:`/game list` 查看可玩游戏,`/game stop` 结束当前游戏,`/game score <游戏名>` 查看游戏排行。 +通用命令:`/game list` 查看可玩游戏,`/game stop` 结束当前游戏,`/game score <游戏名>` 查看游戏排行。群成员均可使用 `/game stop`。21 点在发牌前取消会退款,发牌后强制结束会损失下注;俄罗斯轮盘取消会退还已扣的下注。 --- @@ -43,6 +43,7 @@ ``` 机器人从 1-1000 中随机选一个数字。大家在群里发数字猜: + - 猜小 → 机器人回复“小了,范围 X-1000” - 猜大 → 机器人回复“大了,范围 1-X” - 猜中 → 炸了!范围逐次缩小,60s 无操作自动结束。 @@ -61,15 +62,16 @@ 庄闲对战,金币下注,Bot 坐庄,硬 17 停牌。游戏名可用别名 `blackjack`、`bj`、`21`。 -``` -/game start 21点 200 ← 发起游戏,下注 200 -入场 150 ← 其他人加入(最低为发起者的一半) -入场 100 -开局 ← 发牌(每人 2 张 + 庄家 1 明 1 暗) -拿牌 ← 要牌 -停牌 ← 停牌 -结束 / 结算 ← 手动结算(或全员停牌自动结算) -``` +按以下顺序在群里发消息: + +| 消息 | 操作 | +|---|---| +| `/game start 21点 200` | 发起游戏,下注 200 金币 | +| `入场 150` 或 `入场 100` | 其他人加入,最低为发起者赌注的一半 | +| `开局` | 发牌,每人两张,庄家一明一暗 | +| `拿牌` | 要牌 | +| `停牌` | 停牌 | +| `结束` 或 `结算` | 手动结算;全员停牌也会自动结算 | ### 规则 @@ -82,6 +84,7 @@ | 平局 | 退还本金 | | 庄家爆牌 | 所有未爆牌玩家 1:1 胜出 | | 超时 | 90s 无操作自动结算 | +| 强制结束 | `/game stop` 在发牌前退还下注;发牌后结束会损失下注 | ### 牌面符号 @@ -93,12 +96,14 @@ 1v1 左轮对决,金币下注,7 槽弹仓,1-6 颗子弹。游戏名可用别名 `russian`、`轮盘`、`rr`。 -``` -/game start 俄罗斯轮盘 200 ← 发起对决,赌注 200 -3 ← 装填 3 颗子弹 -接受对决 ← 对手加入(双方各扣 200) -开枪 ← 轮流扣扳机 -``` +按以下顺序在群里发消息: + +| 消息 | 操作 | +|---|---| +| `/game start 俄罗斯轮盘 200` | 发起对决,赌注 200 金币 | +| `3` | 装填三颗子弹 | +| `接受对决` | 对手加入,双方各扣 200 金币 | +| `开枪` | 轮流扣扳机 | ### 规则 @@ -147,8 +152,8 @@ |------|------| | `/牛牛总排行 [N]` | 自然数值排行(有符号排序,正数在前负数在后) | | `/牛牛绝对值排行 [N]` | 绝对值排行(最长和最深都能排前列) | -| `/牛牛长度排行 [N]` | 正数排行(仅 length > 0 的用户) | -| `/牛牛深度排行 [N]` | 负数排行(仅 length < 0 的用户) | +| `/牛牛长度排行 [N]` | 正长度排行 | +| `/牛牛深度排行 [N]` | 负长度排行 | | `/牛牛长度总排行 [N]`、`/牛牛深度总排行 [N]`、`/牛牛绝对值总排行 [N]` | 与对应的长度 / 深度 / 绝对值排行同一份榜单,额外支持私聊查询 | 所有榜单均为部署内全局统计(跨群合并)。默认显示前 10 名,可指定 N(最大 50)。`我的牛牛` 显示的排名因长度正负而异:正数用户只显示总榜名次;负数用户同时显示总榜、深度榜和绝对值榜 3 个名次。长度榜名次不在 `我的牛牛` 中显示。 @@ -170,7 +175,7 @@ | -100~-1000 | 哇哦!进化成魅魔了 | — | | <-1000 | 深渊魅魔之神 | — | -> **牛头人**(length ≥ 50)和 **魅魔**(length ≤ -50)在击剑中有专属特殊效果,见 5.6 节。 +> **牛头人**(长度 ≥ 50)和 **魅魔**(长度 ≤ -50)在击剑中有专属特殊效果,见 5.6 节。 ### 5.5 打胶机制 @@ -178,7 +183,7 @@ | 事件 | 说明 | |------|------| -| 普通 | 长度正常增减,变化量正比于当前绝对值 | +| 普通 | 长度正常增减,变化量随当前长度绝对值的平方根增长 | | 🍀 幸运日 | 运势极佳,大幅增长 | | 🌈 祝福 | 牛牛之神降临,暴增 | | 🧘 贤者模式 | 平静稳定地增长 | @@ -192,7 +197,7 @@ 打胶运势每日自动重掷,分布为对数对称:中位数=1,约 68% 在 [0.1, 10],极端值可能达 1e-9 或 1e9。 -**长度衰减**:当 \|长度\| 超过 50 后,每次打胶/击剑会按衰减率向 0 小幅回归——正侧默认 1%(`decay_rate_high`)、负侧减半为 0.5%,防止数值无限膨胀;\|长度\| ≤ 50 区间使用更慢的 `decay_rate_normal`(默认 0.5%)。这是纯粹的几何衰减,没有 50% 抑制之类的硬性上限。 +**长度调整**:每次打胶/击剑还会按当前长度乘一个调整系数。默认情况下,长度大于 50 时缩短 1%,小于 -50 时绝对值缩小 0.4%;0 到 50 的正长度缩短 0.5%,-50 到 0 的负长度绝对值增加 0.4%。这些比例由部署配置调整。 ### 5.6 击剑机制 @@ -204,17 +209,17 @@ | 💥 暴击 | 大倍率伤害 | | 🔪 擦边 | 小倍率伤害 | | 🔄 翻盘 | 弱者反杀(胜者翻转) | -| 👹 牛头人支配 | **牛头人专属**(length ≥ 50),40% 概率触发腰斩 | -| 👁 魅魔吞噬 | **魅魔专属**(length ≤ -50),窃取对手长度转移给自己 | +| 👹 牛头人支配 | **牛头人专属**(长度 ≥ 50),40% 概率触发腰斩 | +| 👁 魅魔吞噬 | **魅魔专属**(长度 ≤ -50),窃取对手长度转移给自己 | | 🪫 滑倒 | 攻击者自伤 | | 🤝 平局 | 双方小幅减少 | - 牛头人/魅魔事件仅在**胜者拥有对应角色**时激活,否则降级为普通事件 - 攻击或防守均可触发——牛头人/魅魔作为被击方获胜同样激活特殊效果 - 击剑运势影响胜率:运势 > 1 提升,运势 < 1 降低(运势达到 3 后胜率趋于稳定) -- 击剑运势同时放大每剑的输赢幅度,极端运势下的单剑波动有上限护栏 +- 击剑运势同时放大每剑的输赢幅度,极端运势下的单剑波动受上限控制 - 胜率基于双方长度比值计算(差距越大,随机性越趋于 50/50) -- 胜者从败者身上夺取长度,转移量以双方长度的几何均值为基数(默认 10%~15%,失衡对局有补偿下限,可用 `fence_stake_*` 配置调整) +- 胜者从败者身上夺取长度,转移量以双方长度的几何均值为基数(默认 10%~15%,双方差距较大时按补偿下限计算,可由部署者调整) - 被击剑者有 300s 保护期,期间不可再次被击 - 对未注册用户击剑会触发拒绝/强制注册/自伤事件 - 对机器人击剑会生成幻影牛牛(随机长度,不持久化) diff --git a/skills.example/self-docs/references/docs-user-llm-skills.md b/skills.example/self-docs/references/docs-user-llm-skills.md index 3525db04..011317cb 100644 --- a/skills.example/self-docs/references/docs-user-llm-skills.md +++ b/skills.example/self-docs/references/docs-user-llm-skills.md @@ -8,13 +8,13 @@ QuickQuip 的 AI 可以通过 Skill 获得新本事。本文说明 Skill 是什 ## 1. 什么是 Skill -Skill 是部署者给 AI 安装的「扩展能力包」。一个 Skill 教会 AI 一类新本事,例如: +Skill 是部署者给 AI 安装的“扩展能力包”。一个 Skill 教会 AI 一类新本事,例如: - 回答某类问题(机器人自己的用法、配置含义、报错意思等) - 查某类资料(内置的官方文档副本) - 做某类检查(汇报服务器健康状态) -激活 Skill 不需要任何专门命令。你像平时一样用 `/ai` 或 @机器人 提问,AI 觉得问题与某个 Skill 匹配时就会自己用上;整个过程全自动,回复可能稍等多一会儿。Skill 只影响 AI 的回答,复读、彩蛋、游戏等规则功能与它无关。 +部署者启用工具调用与 Skill 后,群友可以直接提问。你像平时一样用 `/ai` 或 @机器人 提问,AI 觉得问题与某个 Skill 匹配时就会自己用上;回复可能稍等多一会儿。Skill 只影响 AI 的回答,复读、彩蛋、游戏等规则功能与它无关。 --- @@ -29,9 +29,9 @@ Skill 是部署者给 AI 安装的「扩展能力包」。一个 Skill 教会 AI 机器人会回复两部分内容: - **已安装的 Skill**:每项列出名字和一段简介; -- **当前会话已激活的 Skill**:本会话里 AI 已经用上的项,没有则显示「(无)」。 +- **当前会话已激活的 Skill**:本会话里 AI 已经用上的项,没有则显示“(无)”。 -这条命令只读,仅用于查看,不会改变任何设置。机器人没装任何 Skill 时,会回复「当前未安装任何 Skill。」。 +机器人没装任何 Skill 时,会回复“当前未安装任何 Skill。”。 --- @@ -57,15 +57,13 @@ AI 会基于内置的官方文档副本作答,覆盖命令用法、游戏玩 - `/ai 内存还剩多少?磁盘快满了吗?` - `/ai 机器人怎么有点卡,是不是内存不足?` -AI 会做一次健康检查,汇报部署主机的负载、内存、磁盘、开机时长等关键数字;哪项数据拿不到,它会如实说明,不会瞎编。 +AI 会做一次健康检查,汇报部署主机的负载、内存、磁盘、开机时长等关键数字;缺失的指标会在结果中标明原因。 --- ## 4. 谁来安装和管理 Skill -Skill 由部署者安装和管理。群友只能用 `/skill list` 查看,安装、更新、删除都只有部署者能做——AI 自己也做不到。 - -机器人没装任何 Skill 时,行为和以前完全一致:聊天、复读、语录、游戏等一切照旧。 +Skill 由部署者通过部署机器或管理后台安装、更新和删除。群友用 `/skill list` 查看已安装项,向 AI 提问时按需使用。 --- diff --git a/skills.example/self-docs/references/docs-user-llm-tool-discovery.md b/skills.example/self-docs/references/docs-user-llm-tool-discovery.md index 471c2138..fed59ed7 100644 --- a/skills.example/self-docs/references/docs-user-llm-tool-discovery.md +++ b/skills.example/self-docs/references/docs-user-llm-tool-discovery.md @@ -2,13 +2,13 @@ # AI 工具发现说明 -QuickQuip 的 AI 在回答前,可能先查找可用的工具再作答。本文说明这一机制的原理和对使用的影响。 +QuickQuip 的 AI 在回答前,可能先查找可用的工具再作答。本文介绍可以使用的能力,以及怎样提问更容易找到合适的工具。 --- ## 1. 什么是工具 -QuickQuip 的 AI 回复不只会聊天,也能在需要时调用工具,例如: +部署者启用工具调用后,AI 可以借助工具完成这些任务: - 查群友身份和别名 - 查已保存的长期记忆 @@ -17,26 +17,32 @@ QuickQuip 的 AI 回复不只会聊天,也能在需要时调用工具,例如 - 画一张矢量图(梗图、图表等,管理员开启后可用) - 调用管理员接入的外部 MCP 能力 -普通闲聊不一定会用工具。只有当问题确实需要外部信息或群内资料时,AI 才会尝试调用。 +AI 根据问题选择要调用的工具。 --- ## 2. 为什么会出现“先找工具” -如果管理员接入了很多外部工具,机器人不会把所有工具说明都塞进每次 AI 请求,以减少无关信息、让模型更专注当前问题。这时 AI 可能先调用 `tool_search` 查找合适的工具,再用找到的工具完成回答;如果它认为工具存在但搜索没有命中,也可能调用 `tool_list` 查看工具组或工具名,再加载具体工具。这个过程不需要你额外操作,只是回复可能多等一轮。 +启用工具发现后,机器人每次先向 AI 提供一部分工具说明。遇到需要其他能力的问题时,AI 会先搜索工具,必要时查看工具分类与名称,再使用找到的工具。这些步骤由机器人完成,回复可能多等一轮。 --- ## 3. 怎么提问更容易命中工具 -直接说清想要的能力,例如 `/ai 帮我查一下这个 GitHub 仓库最近有哪些 PR`、`/ai 搜一下最近关于某个版本的公告`、`/ai 查查群里有没有关于阿桃喜欢什么的记忆`。知道管理员接入了哪类外部工具时,可以把工具来源说出来,如 `/ai 用 GitHub 工具查一下这个项目的 issue`。 +说清任务、查询对象和范围,例如: + +- `/ai 帮我查看 https://github.com/3aKHP/QuickQuip 最近一周有哪些 PR` +- `/ai 搜一下 Python 3.13 的官方发布公告` +- `/ai 查查群里有没有关于阿桃喜欢什么的记忆`(将阿桃换成要查询的群友) + +管理员已接入 GitHub 工具时,也可指定来源:`/ai 用 GitHub 工具列出 3aKHP/QuickQuip 当前仍开放的 issue`。 --- ## 4. 常见情况 -**提示没有找到工具**:通常是当前机器人没有启用对应能力,或者描述太模糊。换个更具体的说法,例如把“查一下那个东西”改成“用 GitHub 查仓库 issue”;知道工具来源时也可以说“列一下 GitHub 工具再找 PR 相关的那个”,机器人会更容易走 `tool_list` 兜底。 +**提示没有找到工具**:通常是当前机器人没有启用对应能力,或者描述太模糊。换个更具体的说法,例如“用 GitHub 查 3aKHP/QuickQuip 当前开放的 issue”;知道工具来源时也可以说“列一下 GitHub 工具再找 PR 相关的那个”,帮助机器人找到对应能力。 -**提示工具结果不足**:工具被找到了,但返回的信息不够完整;AI 不会在结果不足时强行编造。 +**提示工具结果不足**:工具返回的信息不够完整。补充仓库名、日期范围或查询对象,帮助 AI 缩小查询范围。 -**普通聊天不受影响**:工具发现只作用于需要工具的 AI 回复,复读、彩蛋、接龙等规则功能与其无关。 +**回复多等了一会儿**:查找和调用工具会增加处理时间;复读、彩蛋、接龙等规则回复按各自规则运行。 diff --git a/skills.example/self-docs/references/docs-user-private-commands.md b/skills.example/self-docs/references/docs-user-private-commands.md index 299d559e..e131e026 100644 --- a/skills.example/self-docs/references/docs-user-private-commands.md +++ b/skills.example/self-docs/references/docs-user-private-commands.md @@ -35,7 +35,7 @@ 会话行为补充: -- 引用一条消息追问时,当前提问与被引用内容会分开理解;引用里的合并转发会尽量读取其中的正文(转发中的图片不再附带原图,非视觉模型下以文字图注形式进入)。 +- 引用一条消息追问时,当前提问与被引用内容会分开理解。引用合并转发时会尽量读取正文;转发内的原图不会作为图片附件发送,非视觉模型使用文字图注。 - 上下文由会话纪元自动管理:AI 能回看的对话窗口随会话增长(默认上限约 6.4 万 token,冷场后自动收缩);用 `/llm context_limit ` 可把本会话改为固定保留最新 n 条(上限 1024),`reset` 恢复自动管理。 --- @@ -58,7 +58,7 @@ provider 指 AI 的服务来源(如 Gemini、OpenAI),一个 provider 下 | `/llm mcp` | 外部工具(MCP)连接状态(`mcp status` 同此) | | `/skill list` | 查看已安装的 Skill 与当前会话已激活的项(Skill 是部署者安装的 AI 扩展能力包,见 [Skill 说明](llm-skills.md)) | -改动类子命令私聊中任何人可用(群聊中仅管理员): +改动类子命令私聊中任何人可用;群聊中需要群管理、群主或已登记的[全局管理员](../admin/global-admins.md)身份: | 子命令 | 说明 | |------|------| @@ -69,21 +69,23 @@ provider 指 AI 的服务来源(如 Gemini、OpenAI),一个 provider 下 | `/llm memory on / off` | 开关记忆注入 | | `/llm auto_memory on / off / reset / status` | 自动记忆抽取的开关、重置为全局默认、查看 | | `/llm effort <档位>`(`default` / `clear` 恢复跟随渠道配置) | 本会话思考档位覆盖,六档:`low / medium / high / xhigh / max / ultra`;回复会给出实际下发结果(部分模型词表较窄会自动调整);不配置时模型用自身默认档。切换后若回复出现 400 等 4xx 错误,说明该渠道或模型不支持此档位,用 `default` 恢复即可 | -| `/llm delivery intermediate / final / all ` | 分段交付两域的开关:`intermediate` = 中间轮发送(多轮工具回复的过程正文照常发出),`final` = 最终轮分段(最终长回复按自然段拆成多条),`all` = 两域同时操作;`/llm delivery status`(或带域)查看当前值与全局默认,默认两域关闭 | +| `/llm delivery intermediate / final / all ` | 设置回复发送方式:`intermediate` 控制是否发送多轮工具调用中的过程正文,`final` 控制是否把最终长回复按自然段拆成多条,`all` 同时设置这两项;`/llm delivery status`(或带域)查看当前值与全局默认,默认两域关闭 | | `/llm context_limit <条数>`(`reset` 或 `off` 恢复默认) | 把本会话改为固定保留最新 n 条(上限 1024;默认由会话纪元自动管理) | | `/llm clear_context` | 清空短期上下文,“串台”或记错上下文时用 | | `/llm delete_msg` | 引用一条消息发送本命令,或 `/llm delete_msg <消息ID>`,从上下文删除该条 | -| `/llm reload` | 重载全局 LLM 配置,并重置你的上下文条数覆盖 | +| `/llm reload` | 重载已发布 LLM 配置(草稿通过管理后台单独应用),并重置你的上下文条数覆盖 | | `/llm probe` | 并发探活所有 provider | | `/llm mcp reload` | 拉取 MCP 镜像并重连 | -两点注意:`/llm trigger at` 仅群聊有效,私聊只支持前缀触发;`/llm reload` 与 `/llm probe` 虽然私聊任何人可执行,但作用于全局运行时——reload 之后的探活会发一条真实请求,可能产生 provider 计费。 +私聊会话开启后会自动回应普通消息;触发设置中可调整前缀,艾特触发由群聊使用。 + +`/llm reload` 与 `/llm probe` 会重载或探测整个机器人部署的渠道。私聊用户也可以执行;探活会发送真实模型请求,可能产生费用。 --- ## 3. 记忆管理 -长期记忆是跨会话保留的笔记,AI 每次回复时都会参考(即“记忆注入”)。 +长期记忆是跨会话保留的笔记。开启记忆注入后,AI 会在回复时参考;可用 `/llm memory status` 查看开关状态。 | 命令 | 说明 | |------|------| @@ -114,7 +116,7 @@ provider 指 AI 的服务来源(如 Gemini、OpenAI),一个 provider 下 | 功能 | 群聊 | 私聊 | |------|------|------| | 触发 AI | `/ai` 前缀或 @机器人(需规则开启) | 开启会话后自动响应 | -| 会话与上下文 | 共享群上下文,无会话开关 | 独立会话,支持存档与恢复 | +| 会话与上下文 | 群内共享上下文,用 `/llm on / off` 开关 AI | 独立会话,支持存档与恢复 | | AI 配置修改 | 仅管理员 | 任何人 | | 记忆管理 | 仅管理员 | 任何人 | | `/stats`、`/rules` | 可用 | 不可用 | @@ -128,4 +130,4 @@ provider 指 AI 的服务来源(如 Gemini、OpenAI),一个 provider 下 **发消息 AI 不理我?** 先 `/llm status` 确认会话已开启;未开启则 `/start_session`。 -**回复很怪、像“串台”了?** `/llm current` 检查当前配置,`/llm clear_context` 清空短期上下文;仍不正常可用 `/llm delete_msg` 移除具体某条有问题的消息。 +**回复很怪、像“串台”了?** 用 `/llm current` 检查当前配置;引用有问题的消息并发送 `/llm delete_msg`,可定点删除。需要重新开始对话时,用 `/llm clear_context` 清空短期上下文。 diff --git a/skills.example/self-docs/references/docs-user-three-kingdoms-memes.md b/skills.example/self-docs/references/docs-user-three-kingdoms-memes.md index d037b1e3..5c52a5fb 100644 --- a/skills.example/self-docs/references/docs-user-three-kingdoms-memes.md +++ b/skills.example/self-docs/references/docs-user-three-kingdoms-memes.md @@ -6,9 +6,9 @@ --- -## 直接触发(说了就回) +## 关键词触发 -下面这些梗只要消息里出现对应的词,机器人就会接话。 +下面这些梗通过消息里的关键词匹配。规则开启且通过概率和限流检查后,机器人会接话。 | 你说的 | 机器人回 | |---|---| @@ -115,4 +115,4 @@ ## 限流说明 -所有新三国梗共用同一个限流桶,每分钟全群最多触发 12 次,同一个人最多触发 6 次。触发太频繁时机器人会暂时沉默,过一会儿就好了。 +默认配置中,新三国梗共用每群独立的回复配额:每分钟最多 12 次,同一人最多 6 次。命中关键词后还会按配置概率决定是否接话,因此同一句梗可以偶尔触发。触发太频繁时,等待配额恢复后再试。部署者可调整次数和概率。 diff --git a/skills.example/self-docs/references/github-issue_template-memo.md b/skills.example/self-docs/references/github-issue_template-memo.md index 22b77589..970d97f3 100644 --- a/skills.example/self-docs/references/github-issue_template-memo.md +++ b/skills.example/self-docs/references/github-issue_template-memo.md @@ -20,15 +20,15 @@ labels: [memo] ## 现象与锚点 / Observations & anchors - + ## 待厘清的设计问题 / Open design questions -## 边界与非目标 / Boundaries & non-goals +## 约束与取舍 / Constraints & trade-offs - + ## 范围与状态 / Scope & status diff --git a/skills.example/self-docs/references/github-pull_request_template-release.md b/skills.example/self-docs/references/github-pull_request_template-release.md index e3bc476d..22e0259e 100644 --- a/skills.example/self-docs/references/github-pull_request_template-release.md +++ b/skills.example/self-docs/references/github-pull_request_template-release.md @@ -1,7 +1,7 @@