Compare commits

..

No commits in common. "2e063e2ac0614d778a1070a6b82a39c2d2aa5622" and "c6232d832551a399fc4e8d9050e6b9031ee1ab59" have entirely different histories.

3 changed files with 6 additions and 15 deletions

View File

@ -87,11 +87,11 @@ Session = 消息列表,ORM 直写 PG `messages`(append-only,jsonb 存 LiteLLM
### 3.8 个人知识库(`core/kb.py` + `core/kb_ingest.py`,✅ 2026-07-22) ### 3.8 个人知识库(`core/kb.py` + `core/kb_ingest.py`,✅ 2026-07-22)
用户自建资料(规范/报告/标准/内部文档)的长期查阅层,与 §3.7 记忆同范式:**纯文件 + prompt 注入契约,无向量无 DB**(判据同"真实文件为准":个人库几十~百余文件,agentic search 足够;索引若引入只能是可重建派生缓存)。**两层格局**:zcbot 内建 `.kb/` 私有小库(本节)+ 院检索服务共享大库(document_search,zcbot 只当客户端)——分工标准 = 文件数 × 查询频次,路由靠各自工具/契约描述自然分流,不在 kb 契约里点名 document_search(2026-07-22 收窄:契约只管自己怎么用,少一层耦合)。 用户自建资料(规范/报告/标准/内部文档)的长期查阅层,与 §3.7 记忆同范式:**纯文件 + prompt 注入契约,无向量无 DB**(判据同"真实文件为准":个人库几十~百余文件,agentic search 足够;索引若引入只能是可重建派生缓存)。**两层格局**:zcbot 内建 `.kb/` 私有小库(本节)+ 院检索服务共享大库(document_search,zcbot 只当客户端)——分工标准 = 文件数 × 查询频次,路由规则写进注入契约(学科文献→院库 / 自建资料→`.kb`)。
- **做成机制而非 skill**(判据:有独立于会话的持久状态需用户管理 → 机制):落盘 `user_root/.kb/<库名>/`(INDEX.md + docs/ 转换后 md + sources/ 原件)。**"已入库"判据 = INDEX.md 有条目**,sources 有而 INDEX 无 = 待入库 → 入库幂等、崩溃可恢复、零 migration。dotfile 命名同 `.memory` 双向防呆,GET /v1/files 天然隐藏。 - **做成机制而非 skill**(判据:有独立于会话的持久状态需用户管理 → 机制):落盘 `user_root/.kb/<库名>/`(INDEX.md + docs/ 转换后 md + sources/ 原件)。**"已入库"判据 = INDEX.md 有条目**,sources 有而 INDEX 无 = 待入库 → 入库幂等、崩溃可恢复、零 migration。dotfile 命名同 `.memory` 双向防呆,GET /v1/files 天然隐藏。
- **入库管线**(`core/kb_ingest.py`,上传即触发 + 手动兜底):markitdown Python API 转 md → 扫描件 PDF(文本近零)走方舟文档理解 OCR 兜底(§8.13 同通道)→ deepseek flash 单次 chat 写 标题/摘要/关键词(失败降级文件名+正文开头,不阻塞)→ 追加 INDEX 行。编排照定时执行器:create_task + to_thread + per-(user,库) 内存锁去重;进度存内存供前端轮询,崩了靠 FS 判据续跑。 - **入库管线**(`core/kb_ingest.py`,上传即触发 + 手动兜底):markitdown Python API 转 md → 扫描件 PDF(文本近零)走方舟文档理解 OCR 兜底(§8.13 同通道)→ deepseek flash 单次 chat 写 标题/摘要/关键词(失败降级文件名+正文开头,不阻塞)→ 追加 INDEX 行。编排照定时执行器:create_task + to_thread + per-(user,库) 内存锁去重;进度存内存供前端轮询,崩了靠 FS 判据续跑。
- **agent 侧零新工具**:`kb_block`(照 memory_block)把 INDEX 全文 + 契约(主动查阅无需点名 / INDEX 行格式 / 答题标来源)注 prompt,**用户有库才注入**;fs 工具在 user_root 内可读写、docker 沙箱整 user_root bind → `.kb` 天然可达。INDEX 行格式是对话内手动入库与后台产出的同一契约。 - **agent 侧零新工具**:`kb_block`(照 memory_block)把 INDEX 全文 + 契约(路由规则 / INDEX 行格式 / 答题标来源)注 prompt,**用户有库才注入**;fs 工具在 user_root 内可读写、docker 沙箱整 user_root bind → `.kb` 天然可达。INDEX 行格式是对话内手动入库与后台产出的同一契约。
- **API 薄壳**(`/v1/kb*` 8 端点):列/建/删库、详情(带入库进度)、上传即入库、手动 ingest、看/删单篇。**不设 HTTP 检索端点**——检索是 agent 的事。前端两栏 modal(kb.js)管上传/删除,查询全走对话。 - **API 薄壳**(`/v1/kb*` 8 端点):列/建/删库、详情(带入库进度)、上传即入库、手动 ingest、看/删单篇。**不设 HTTP 检索端点**——检索是 agent 的事。前端两栏 modal(kb.js)管上传/删除,查询全走对话。
- **记账**:`usage_events` kind="kb_ingest"(OCR 那笔走 kind="vision"),无 task 上下文 → 0022 放宽 task_id 可 NULL,溯源靠 units JSONB `{"kb", "source"}` - **记账**:`usage_events` kind="kb_ingest"(OCR 那笔走 kind="vision"),无 task 上下文 → 0022 放宽 task_id 可 NULL,溯源靠 units JSONB `{"kb", "source"}`

View File

@ -18,7 +18,6 @@ state.json 已删除(元数据全在 PG)。
from __future__ import annotations from __future__ import annotations
import os import os
import sys
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Optional, Tuple from typing import Any, Callable, Optional, Tuple
@ -380,14 +379,6 @@ def _build_system_prompt(
wd_path = "/workspace" wd_path = "/workspace"
else: else:
wd_path = str(wd_abs) wd_path = str(wd_abs)
# 渲染器调用命令按 backend 换:docker 是容器内 bind 的 /sandbox 路径 + 容器 python;
# host(本地 dogfood)按宿主真实路径 + 当前解释器(venv,依赖齐)—— 否则 agent 拿着
# 容器路径在宿主上调,必 FileNotFoundError。skill 文档里的 /sandbox 写法是 docker
# 专属管线,不受此影响。
if is_docker:
render_cmd = "python /sandbox/rendering/render.py"
else:
render_cmd = f'"{sys.executable}" "{ROOT / "rendering" / "render.py"}"'
today = datetime.now().strftime("%Y-%m-%d") today = datetime.now().strftime("%Y-%m-%d")
tname = task_name or "<未指定>" tname = task_name or "<未指定>"
short_id = task_id.hex[:8] short_id = task_id.hex[:8]
@ -423,7 +414,7 @@ def _build_system_prompt(
f"**优先**把正文写成 Markdown(`<task_dir>/sections/*.md`,纯文本、零转义 / 零语法风险)," f"**优先**把正文写成 Markdown(`<task_dir>/sections/*.md`,纯文本、零转义 / 零语法风险),"
f"再调平台渲染器出 docx —— **别在 run_python 里手撸 python-docx 内联大段中文正文**" f"再调平台渲染器出 docx —— **别在 run_python 里手撸 python-docx 内联大段中文正文**"
f"(中文引号 / 全角标点混进源码极易 SyntaxError、反复返工烧 token):\n\n" f"(中文引号 / 全角标点混进源码极易 SyntaxError、反复返工烧 token):\n\n"
f" {render_cmd} --profile report --format docx <task_dir>/sections/ -o <task_dir>/<报告名>.docx\n\n" f" python /sandbox/rendering/render.py --profile report --format docx <task_dir>/sections/ -o <task_dir>/<报告名>.docx\n\n"
f"渲染器自动管字体(正文宋体小四 / 标题黑体 / 1.5 倍行距)、目录(加 `--toc`)、" f"渲染器自动管字体(正文宋体小四 / 标题黑体 / 1.5 倍行距)、目录(加 `--toc`)、"
f"表格、`![](path)` 居中插图 + 图题编号、mermaid 图。已加载 proposal / paper 等 skill 时" f"表格、`![](path)` 居中插图 + 图题编号、mermaid 图。已加载 proposal / paper 等 skill 时"
f"按其 render 指引的 profile 走(更具体),通用报告用 `--profile report`。\n" f"按其 render 指引的 profile 走(更具体),通用报告用 `--profile report`。\n"

View File

@ -241,9 +241,9 @@ def save_source(workspace_dir: Path, user_id: UUID, name: str, filename: str, da
_KB_CONTRACT = """\ _KB_CONTRACT = """\
用法规矩: 用法规矩:
- **主动查阅**:回答前先扫一眼下方各库条目,标题 / 摘要 / 关键词与用户问题相关就先 - **路由**:**材料学科学术文献**优先 `document_search`(院内共享大库);**用户自建
`read` 对应 `docs/*.md` 正文再作答(量大时 `grep` 先缩范围)**无需用户点名 资料**(个人上传的规范 / 报告 / 标准 / 内部文档)用下面的知识库 先看各库 INDEX
"知识库"** 用户资料里可能已有的内容,宁可多查一次,不要凭空回答 条目定位, `read` 对应 `docs/*.md` 正文,量大时 `grep` 先缩范围
- **答题标来源**:引用了哪个库哪篇就在回答里注明(标题或文件名) - **答题标来源**:引用了哪个库哪篇就在回答里注明(标题或文件名)
- **对话内入库**:用户在对话里给了值得长期留的资料时,可直接写入:原件放 - **对话内入库**:用户在对话里给了值得长期留的资料时,可直接写入:原件放
`sources/`(没有原件就跳过)正文转成 markdown `docs/<slug>.md`再往该库 `sources/`(没有原件就跳过)正文转成 markdown `docs/<slug>.md`再往该库