Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/admin/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ LLM 工具 `search_web` 与 `/search` 命令固定走项目内 SearXNG。普通
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `LLM_TRACE_FLAG_FILE` | LLM HTTP Trace 持久开关文件路径;文件存在时按调用记录完整请求/响应 JSON 文本到 `data/llm_trace.db`,供 Web Admin 的 LLM Trace 页面按需读取 | — |
| `LLM_TRACE_MEDIA_MIN_BYTES` | Trace 内联媒体抽取门槛(载荷原文 UTF-8 字节数),推荐启用值 `65536`;未设置或为 `0` 时关闭。Bot 和 Web 在启动时读取。操作见下方“Trace 媒体存储” | `0` |

### Trace 媒体存储

Trace 开启期间,可在根 `.env` 设置 `LLM_TRACE_MEDIA_MIN_BYTES=65536` 并重启 Bot 与 Web,对新记录中达到门槛的内联媒体去重。正文显示占位符,原始载荷保存在 `data/trace_blobs/`;既有记录继续按原格式读取。完整 JSON 与 SSE 单行 JSON 中的 data URI 和明确媒体字段受支持。

备份与恢复时,将 `data/llm_trace.db` 和 `data/trace_blobs/` 作为一组:停服后同时备份,恢复时同时还原。部署模板挂载整个 `data/`,无需额外挂载。不要把多个独立 Trace 数据库指向同一个 blob 目录。

设置为 `0` 并重启会停止新记录的媒体抽取,当前版本仍能恢复已抽取记录。回退到不支持该清单格式的旧版本时,旧程序会显示占位符;需要完整历史报文时,应先在支持此功能的版本中导出原文,或恢复启用前的 Trace 备份。已有大记录的批量转换另行安排。

管理员可通过 `/ops/api/logs/trace/calls/{call_id}?restore=true` 获取经校验的完整正文;`call_id` 使用详情记录中的实际 ID。`/ops/api/logs/trace/blobs/{sha256}` 使用占位符中的摘要下载媒体附件,两个接口均要求有效的后台登录态。文件缺失或恢复校验失败时,先检查数据库与 blob 目录是否来自同一备份。清空 Trace 会移除记录并清理无引用媒体文件;下载只开放给仍被记录引用的载荷。

### 贴吧

Expand Down
8 changes: 8 additions & 0 deletions docs/dev/llm-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ QuickQuip 的 LLM 模块为规则机器人提供对话扩展。管理员按群

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 调用按组内序号排列。

启用 `LLM_TRACE_MEDIA_MIN_BYTES` 后,Trace 会在入库前抽取达到门槛的内联 base64 载荷。识别范围为完整 JSON、SSE 的完整单行 JSON data 记录及其中最多四层嵌套 JSON 字符串;data URI 须通过严格 base64 校验,裸编码还须处于明确的媒体字段,例如 `b64_json`、`partial_image_b64`、带媒体类型的 `data` 或图片生成结果。普通正文、未达门槛、畸形及不支持的编码形态按原文保存。

载荷原文按 SHA-256 存入 `data/trace_blobs/`,正文替换为 `[qq:blob sha256=<hex> bytes=<n>]`。完全相同的载荷文本跨请求、重组响应和原始 SSE 共用一个文件;data URI 前缀参与哈希。每个正文的替换位置和哈希保存在同一条记录的 `media_truncation` 清单中,原始字节计数保持原义。请求发送路径使用原始载荷。

每份 blob 目录由一份 Trace 数据库拥有。Bot 和 Web 通过该数据库的 `BEGIN IMMEDIATE` 写事务协调载荷创建/复用、引用提交,以及 GC 引用扫描到文件删除的整个区间。每日留存清理后删除无引用且超过一天的文件;文件年龄用于保留失败写入的恢复窗口,并发安全由事务保证。清单无效时跳过该次文件清理。显式清空会删除记录并清理无引用文件,下载入口同时检查记录引用。

管理员详情接口默认返回含占位符的正文,`restore=true` 按清单恢复并核验全文与载荷哈希;文件缺失或校验失败返回 409。blob 下载继承管理员鉴权,统一返回 `application/octet-stream` 附件,并设置 `nosniff` 与限制性 CSP。抽取开关关闭后仍能恢复已抽取记录。配置、备份及升级操作见[管理员配置](../admin/configuration.md#trace-媒体存储)。

### 1.1 执行记录的请求边界

群聊和私聊生成请求创建的执行记录按请求携带的 `trigger_kind` 保存触发类型;未显式指定时,群聊默认为 `group_direct`,私聊默认为 `private_direct`。被动群触发保存为 `group_passive`,供 Loop 详情与历史档案使用。历史记录保留已存分类,缺少原始触发证据时不推断回填。
Expand Down
11 changes: 11 additions & 0 deletions skills.example/self-docs/references/docs-admin-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ LLM 工具 `search_web` 与 `/search` 命令固定走项目内 SearXNG。普通
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `LLM_TRACE_FLAG_FILE` | LLM HTTP Trace 持久开关文件路径;文件存在时按调用记录完整请求/响应 JSON 文本到 `data/llm_trace.db`,供 Web Admin 的 LLM Trace 页面按需读取 | — |
| `LLM_TRACE_MEDIA_MIN_BYTES` | Trace 内联媒体抽取门槛(载荷原文 UTF-8 字节数),推荐启用值 `65536`;未设置或为 `0` 时关闭。Bot 和 Web 在启动时读取。操作见下方“Trace 媒体存储” | `0` |

### Trace 媒体存储

Trace 开启期间,可在根 `.env` 设置 `LLM_TRACE_MEDIA_MIN_BYTES=65536` 并重启 Bot 与 Web,对新记录中达到门槛的内联媒体去重。正文显示占位符,原始载荷保存在 `data/trace_blobs/`;既有记录继续按原格式读取。完整 JSON 与 SSE 单行 JSON 中的 data URI 和明确媒体字段受支持。

备份与恢复时,将 `data/llm_trace.db` 和 `data/trace_blobs/` 作为一组:停服后同时备份,恢复时同时还原。部署模板挂载整个 `data/`,无需额外挂载。不要把多个独立 Trace 数据库指向同一个 blob 目录。

设置为 `0` 并重启会停止新记录的媒体抽取,当前版本仍能恢复已抽取记录。回退到不支持该清单格式的旧版本时,旧程序会显示占位符;需要完整历史报文时,应先在支持此功能的版本中导出原文,或恢复启用前的 Trace 备份。已有大记录的批量转换另行安排。

管理员可通过 `/ops/api/logs/trace/calls/{call_id}?restore=true` 获取经校验的完整正文;`call_id` 使用详情记录中的实际 ID。`/ops/api/logs/trace/blobs/{sha256}` 使用占位符中的摘要下载媒体附件,两个接口均要求有效的后台登录态。文件缺失或恢复校验失败时,先检查数据库与 blob 目录是否来自同一备份。清空 Trace 会移除记录并清理无引用媒体文件;下载只开放给仍被记录引用的载荷。

### 贴吧

Expand Down
8 changes: 8 additions & 0 deletions skills.example/self-docs/references/docs-dev-llm-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ QuickQuip 的 LLM 模块为规则机器人提供对话扩展。管理员按群

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 调用按组内序号排列。

启用 `LLM_TRACE_MEDIA_MIN_BYTES` 后,Trace 会在入库前抽取达到门槛的内联 base64 载荷。识别范围为完整 JSON、SSE 的完整单行 JSON data 记录及其中最多四层嵌套 JSON 字符串;data URI 须通过严格 base64 校验,裸编码还须处于明确的媒体字段,例如 `b64_json`、`partial_image_b64`、带媒体类型的 `data` 或图片生成结果。普通正文、未达门槛、畸形及不支持的编码形态按原文保存。

载荷原文按 SHA-256 存入 `data/trace_blobs/`,正文替换为 `[qq:blob sha256=<hex> bytes=<n>]`。完全相同的载荷文本跨请求、重组响应和原始 SSE 共用一个文件;data URI 前缀参与哈希。每个正文的替换位置和哈希保存在同一条记录的 `media_truncation` 清单中,原始字节计数保持原义。请求发送路径使用原始载荷。

每份 blob 目录由一份 Trace 数据库拥有。Bot 和 Web 通过该数据库的 `BEGIN IMMEDIATE` 写事务协调载荷创建/复用、引用提交,以及 GC 引用扫描到文件删除的整个区间。每日留存清理后删除无引用且超过一天的文件;文件年龄用于保留失败写入的恢复窗口,并发安全由事务保证。清单无效时跳过该次文件清理。显式清空会删除记录并清理无引用文件,下载入口同时检查记录引用。

管理员详情接口默认返回含占位符的正文,`restore=true` 按清单恢复并核验全文与载荷哈希;文件缺失或校验失败返回 409。blob 下载继承管理员鉴权,统一返回 `application/octet-stream` 附件,并设置 `nosniff` 与限制性 CSP。抽取开关关闭后仍能恢复已抽取记录。配置、备份及升级操作见[管理员配置](../admin/configuration.md#trace-媒体存储)。

### 1.1 执行记录的请求边界

群聊和私聊生成请求创建的执行记录按请求携带的 `trigger_kind` 保存触发类型;未显式指定时,群聊默认为 `group_direct`,私聊默认为 `private_direct`。被动群触发保存为 `group_passive`,供 Loop 详情与历史档案使用。历史记录保留已存分类,缺少原始触发证据时不推断回填。
Expand Down
2 changes: 1 addition & 1 deletion skills.example/self-docs/references/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
- `docs/dev/game-framework.md` → `references/docs-dev-game-framework.md` — 游戏框架开发者指南 | 关键词:BaseGame、aliases、src/quickquip/app/message_pipeline.py、GameResult、finished、group_id、OrderedDict[str, Session]、src/quickquip/adapters/nonebot/commands.py、command_parts/、_ensure_schema()
- `docs/dev/llm-configuration-api.md` → `references/docs-dev-llm-configuration-api.md` — LLM 配置后端接口 | 关键词:data/llm_config.db、/ops/api/llm-configuration、quickquip_admin_session、Origin、Referer、在进程内构造应用导出,随约束或路由变更同步提交;运行中的管理 API 不提供、/redoc、/openapi.json、format_version、。顶层字段为
- `docs/dev/llm-configuration-frontend.md` → `references/docs-dev-llm-configuration-frontend.md` — LLM 配置管理前端交付契约 | 关键词:/ops/#/llm-config、draft_revision、published_revision、loaded_revision、draft_version、published_version、loaded_version、{saved_at, note}、PUT /note、202
- `docs/dev/llm-module.md` → `references/docs-dev-llm-module.md` — QuickQuip LLM 模块说明 | 关键词:/search、LLM_TRACE_FLAG_FILE、data/llm_trace.db、run_tool_call_loop、trigger_kind、group_direct、private_direct、group_passive、interrupted、request_cancelled
- `docs/dev/llm-module.md` → `references/docs-dev-llm-module.md` — QuickQuip LLM 模块说明 | 关键词:/search、LLM_TRACE_FLAG_FILE、data/llm_trace.db、run_tool_call_loop、LLM_TRACE_MEDIA_MIN_BYTES、b64_json、partial_image_b64、data、data/trace_blobs/、[qq:blob sha256=<hex> bytes=<n>]
- `docs/dev/mcp-integration.md` → `references/docs-dev-mcp-integration.md` — QuickQuip MCP 集成说明 | 关键词:ToolRegistry、data/llm_config.db、settings.mcp、config.toml、search_web、tavily_search、tavily_crawl、tavily_research、docker、/var/run/docker.sock
- `docs/dev/mcp-tutorial.md` → `references/docs-dev-mcp-tutorial.md` — 从零理解 MCP —— 以 QuickQuip 项目为例 | 关键词:settings.mcp.servers、llm-module.md、src/quickquip/llm/mcp/、src/quickquip/llm/config_document.py、src/quickquip/llm/service_parts/mcp_lifecycle.py、。配置权威模板为、MCPClient、mcp/client.py、ToolRegistry、mcp_*
- `docs/dev/README.md` → `references/docs-dev-readme.md` — QuickQuip 开发者文档 | 关键词:docs/dev/、../glossary.md、adr/README.md、documenting.md、architecture.md、style.md、testing.md、branching.md、versioning.md、record-identities.md
Expand Down
51 changes: 46 additions & 5 deletions src/quickquip/app/web/routes/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import time

from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.responses import FileResponse, Response, StreamingResponse

from quickquip.common.paths import LOGS_DIR
from quickquip.llm.provider import trace_store
Expand All @@ -17,6 +17,7 @@

_LOGS_DIR = LOGS_DIR
_LOG_FILE_RE = re.compile(r"^quickquip_\d{4}-\d{2}-\d{2}\.log$")
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
_STREAM_POLL_SECONDS = 1.0
_STREAM_HEARTBEAT_SECONDS = 20.0
_MAX_TAIL_LINES = 500
Expand Down Expand Up @@ -225,10 +226,50 @@ def list_trace_calls(


@router.get("/logs/trace/calls/{call_id}")
def get_trace_call(call_id: str):
"""Load the complete HTTP text payload for one selected Trace call."""

entry = trace_store.get_call(call_id)
def get_trace_call(call_id: str, restore: bool = False):
"""Load one Trace call, optionally splicing media blobs back in.

默认返回含 ``[qq:blob sha256=... bytes=...]`` 占位符的文本(文本量小,
前端不必渲染数 MB JSON);``restore=true`` 时按 manifest 回拼并校验,
返回字节级原始报文,重放验收场景直接可用。

``restore`` 刻意用普通默认值而不是 ``Query(default=False)``:后者在
FastAPI 之外被直接调用时拿到的是 Query 对象(真值),会把默认路径走成
restore 分支。普通 ``bool`` 默认值 FastAPI 同样按查询参数解析。
"""

if not restore:
entry = trace_store.get_call(call_id)
if entry is None:
raise HTTPException(status_code=404, detail="trace call not found")
return entry
try:
entry = trace_store.restore_call(call_id)
except ValueError as error:
raise HTTPException(
status_code=409, detail=f"trace restore failed: {error}"
) from error
if entry is None:
raise HTTPException(status_code=404, detail="trace call not found")
return entry


@router.get("/logs/trace/blobs/{sha256}")
def get_trace_blob(sha256: str):
"""Download one content-addressed media blob by its sha256 digest."""

if not _SHA256_RE.fullmatch(sha256):
raise HTTPException(status_code=422, detail="invalid blob digest")
result = trace_store.download_blob(sha256)
if result is None:
raise HTTPException(status_code=404, detail="blob not found")
data, _ = result
return Response(
content=data,
media_type="application/octet-stream",
headers={
"Content-Disposition": f'attachment; filename="{sha256}.bin"',
"X-Content-Type-Options": "nosniff",
"Content-Security-Policy": "default-src 'none'; sandbox",
},
)
Loading
Loading