---
date: 2026-06-23
status: active
origin: docs/brainstorms/2026-06-23-document-processing-capability-requirements.md
---

# feat: Document Processing Capability

## Summary

为 AgentKit 增加文档处理能力，v1 聚焦 Word/Excel/PDF 三种格式的创建和读取，以及 Word 模板填充。通过自研 DocumentService 统一封装所有文档操作（python-docx/openpyxl/reportlab/python-docx-template），Agent 工具和前端 REST API 共用同一套业务逻辑。生成的文档保存在服务器并持久化元数据，对话中返回文件卡片，同时在右侧面板展示当前对话的文档列表。

## Problem Frame

当前 Agent 工具集没有格式化文档处理能力。用户需要生成报告、合同、数据表等文档时，Agent 只能通过 shell 创建纯文本文件。

原计划集成 MCP Document Tools，但功能验证发现：版本 0.1.0 未验证状态不建议生产使用、不支持模板填充（核心需求）、Office→PDF 仅限 docx。因此改为全部自研，使用成熟的 python-docx/openpyxl/reportlab/python-docx-template 库，完全可控且无外部依赖风险。

## Requirements

Traceability to origin requirements doc (R-IDs preserved):

- R1-R4: 文档处理能力（Word/Excel/PDF 创建 + 读取）
- R5-R8: Agent 工具集成
- R9-R10: 前端界面
- R11-R16: 文件存储与生命周期
- R17-R18: 对话中文档展示
- R19-R22: 右侧文档/附件面板
- R23-R25: 模板填充（Word only）
- R26-R28: 安全

## Key Technical Decisions

- **自研而非 MCP 集成** — MCP Document Tools 版本 0.1.0 未验证、不支持模板填充、不建议生产使用。改用成熟的生产级库：python-docx（Word）、openpyxl（Excel）、reportlab（PDF）、python-docx-template（Word 模板填充）。MCP Document Tools 降级为可选增强，不在 v1 范围。
- **DocumentService 统一封装** — DocumentService 作为唯一业务逻辑层，Agent 工具和前端 REST API 都是薄封装。内部按格式分派到对应的 renderer 模块。
- **Agent 生成 Markdown，Service 负责格式映射** — Agent 生成 Markdown 格式的结构化内容，DocumentService 内部有 Markdown→Word/Excel/PDF 的 renderer，将 Markdown 结构映射为目标格式。Agent 不直接操作 Office XML。
- **数据库用 aiosqlite 裸连接** — 遵循项目现有模式（auth.py 的 `aiosqlite.connect`），不引入 SQLAlchemy session 依赖注入。文档元数据表用原生 SQL 建表。
- **Jinja2 沙箱化** — 模板填充使用 `jinja2.sandbox.SandboxedEnvironment`，防止 SSTI 攻击。
- **文件存储复用 data/uploads/** — 复用现有上传目录和 `_sanitize_filename` 函数，但下载 API 新增认证。

---

## Implementation Units

### U1. DocumentService 核心架构 + 数据库模型

**Goal:** 建立 DocumentService 骨架和文档元数据持久化基础。

**Requirements:** R11, R13, R14, R15, R16

**Dependencies:** 无

**Files:**
- `src/agentkit/documents/__init__.py`（新建）
- `src/agentkit/documents/service.py`（新建）
- `src/agentkit/documents/models.py`（新建）
- `src/agentkit/documents/db.py`（新建）
- `pyproject.toml`（修改：添加 python-docx, openpyxl, reportlab, docxtpl, jinja2 依赖）

**Approach:**
- `DocumentService` 类：`create_document(format, content, conversation_id, template_path?) -> DocumentMeta`、`get_conversation_documents(conversation_id) -> list[DocumentMeta]`、`get_download_path(doc_id) -> Path`
- `DocumentMeta` dataclass：`id, filename, stored_name, format, size, conversation_id, created_at, download_url`
- 数据库表 `documents`：id (UUID), filename, stored_name, format, size, conversation_id, created_at。用 aiosqlite 裸连接，`init_documents_db()` 建表。
- 文件存储：UUID + 扩展名，存到 `data/uploads/`，复用 `_sanitize_filename`。

**Patterns to follow:** `src/agentkit/server/auth/models.py`（aiosqlite 模式）、`src/agentkit/server/routes/chat.py` 的 `_sanitize_filename` 函数。

**Test scenarios:**
- Happy path: 创建文档元数据记录，查询返回正确数据
- Edge case: 不存在的 conversation_id 返回空列表
- Edge case: 文件名包含路径遍历字符（../）被清洗
- Integration: init_documents_db 幂等（重复调用不报错）

**Verification:** 运行 `pytest tests/documents/test_db.py`，确认元数据 CRUD 和文件存储正常。

---

### U2. Word 文档创建（python-docx + Markdown→Word 映射）

**Goal:** 实现 Markdown→Word 的格式映射，Agent 生成 Markdown 内容，DocumentService 生成 .docx 文件。

**Requirements:** R1

**Dependencies:** U1

**Files:**
- `src/agentkit/documents/renderers/__init__.py`（新建）
- `src/agentkit/documents/renderers/word_renderer.py`（新建）
- `tests/documents/test_word_renderer.py`（新建）

**Approach:**
- `WordRenderer.render(markdown_content: str, output_path: Path) -> Path`
- Markdown 解析：用 `markdown` 库解析为 AST，遍历 AST 映射到 python-docx 对象：
  - `# 标题` → `doc.add_heading(text, level=1)`
  - `## 二级标题` → `doc.add_heading(text, level=2)`
  - 段落 → `doc.add_paragraph(text)`
  - `- 列表项` → `doc.add_paragraph(text, style='List Bullet')`
  - `1. 有序列表` → `doc.add_paragraph(text, style='List Number')`
  - Markdown 表格 → `doc.add_table(rows, cols)` + 填充
  - `**粗体**` → run with `bold=True`
  - `*斜体*` → run with `italic=True`

**Patterns to follow:** python-docx 官方文档的基本用法。

**Test scenarios:**
- Happy path: 包含标题、段落、列表、表格的 Markdown 生成正确的 .docx
- Edge case: 空 Markdown 生成空文档（只有标题或完全空）
- Edge case: 嵌套格式（粗体+斜体混合）正确渲染
- Error path: 无效 Markdown 不崩溃，按纯文本处理

**Verification:** 运行 `pytest tests/documents/test_word_renderer.py`，打开生成的 .docx 确认格式正确。

---

### U3. Excel 文档创建（openpyxl + Markdown 表格→Excel 映射）

**Goal:** 实现 Markdown 表格/JSON→Excel 的格式映射。

**Requirements:** R2

**Dependencies:** U1

**Files:**
- `src/agentkit/documents/renderers/excel_renderer.py`（新建）
- `tests/documents/test_excel_renderer.py`（新建）

**Approach:**
- `ExcelRenderer.render(markdown_content: str, output_path: Path) -> Path`
- 解析 Markdown 中的表格（`| col1 | col2 |` 格式），每个表格映射到一个 worksheet
- 非表格文本（标题、段落）作为注释行或单独的 "Summary" sheet
- 支持 JSON 格式输入：`{"Sheet1": [["A1","B1"],["A2","B2"]]}`（当 content 是有效 JSON 时走 JSON 路径）

**Patterns to follow:** openpyxl 官方文档的基本用法。

**Test scenarios:**
- Happy path: Markdown 表格生成正确的 .xlsx，数据对齐
- Happy path: JSON 格式输入生成多 sheet Excel
- Edge case: 无表格的 Markdown 生成单 sheet 纯文本
- Edge case: 多个表格生成多个 sheet

**Verification:** 运行 `pytest tests/documents/test_excel_renderer.py`，打开生成的 .xlsx 确认数据正确。

---

### U4. PDF 文档创建（reportlab + Markdown→PDF 映射）

**Goal:** 实现 Markdown→PDF 的格式映射，使用 reportlab 生成 PDF。

**Requirements:** R3

**Dependencies:** U1

**Files:**
- `src/agentkit/documents/renderers/pdf_renderer.py`（新建）
- `tests/documents/test_pdf_renderer.py`（新建）

**Approach:**
- `PDFRenderer.render(markdown_content: str, output_path: Path) -> Path`
- 用 reportlab 的 `SimpleDocTemplate` + `Paragraph` + `Table` + `ListFlowable`
- Markdown 解析同 U2，映射到 reportlab flowables：
  - `# 标题` → `Paragraph(text, Heading1 style)`
  - 段落 → `Paragraph(text, Normal style)`
  - 列表 → `ListFlowable([ListItem(...)])`
  - 表格 → `Table(data)` + 基础样式
  - `**粗体**` → `<b>text</b>`（reportlab Paragraph 支持 HTML 标签）

**Patterns to follow:** reportlab 官方文档。

**Test scenarios:**
- Happy path: 包含标题、段落、列表、表格的 Markdown 生成正确的 PDF
- Edge case: 空 Markdown 生成空白 PDF
- Edge case: 中文字符正确渲染（需注册中文字体）
- Error path: 无效 Markdown 不崩溃

**Verification:** 运行 `pytest tests/documents/test_pdf_renderer.py`，打开生成的 PDF 确认格式和中文渲染。

---

### U5. Word 模板填充（python-docx-template + Jinja2 沙箱）

**Goal:** 实现 Word 模板填充，用户上传 .docx 模板，Agent 提供数据，填充 Jinja2 占位符。

**Requirements:** R23, R24, R25, R26

**Dependencies:** U1, U2

**Files:**
- `src/agentkit/documents/renderers/template_renderer.py`（新建）
- `tests/documents/test_template_renderer.py`（新建）

**Approach:**
- `TemplateRenderer.render(template_path: Path, data: dict, output_path: Path) -> Path`
- 用 `docxtpl.DocxTemplate(template_path)` 加载模板
- 用 `jinja2.sandbox.SandboxedEnvironment` 创建沙箱环境
- `template.render(data)` 填充数据
- 支持 `{{variable}}`、`{% if %}`、`{% for %}` 基本控制结构

**Patterns to follow:** python-docx-template 官方文档。

**Test scenarios:**
- Happy path: 模板包含 `{{name}}`，data=`{"name":"张三"}`，输出文档中 "张三" 替换占位符
- Happy path: `{% for item in items %}` 循环正确展开
- Happy path: `{% if condition %}` 条件渲染正确
- Security: SSTI 攻击 payload（`{{config.__class__}}`）被沙箱拦截
- Edge case: 模板无占位符时原样输出
- Error path: data 缺少变量时，占位符保持原样或清空（不崩溃）

**Verification:** 运行 `pytest tests/documents/test_template_renderer.py`，确认填充和沙箱安全。

---

### U6. Agent 工具封装（DocumentTool）

**Goal:** 创建 Agent 工具，LLM 通过 function calling 触发文档创建。

**Requirements:** R5, R6, R7, R8

**Dependencies:** U1, U2, U3, U4, U5

**Files:**
- `src/agentkit/tools/document_tool.py`（新建）
- `src/agentkit/server/app.py`（修改：注册 DocumentTool）
- `tests/tools/test_document_tool.py`（新建）

**Approach:**
- `DocumentTool(service: DocumentService)` 继承 `Tool`
- `name = "document"`，`description = "创建格式化文档（Word/Excel/PDF）或填充 Word 模板"`
- `input_schema`：
  ```json
  {
    "type": "object",
    "properties": {
      "format": {"type": "string", "enum": ["word", "excel", "pdf"]},
      "content": {"type": "string", "description": "Markdown 格式的文档内容"},
      "template": {"type": "string", "description": "模板文件路径（可选，仅 word）"},
      "template_data": {"type": "object", "description": "模板填充数据（可选）"}
    },
    "required": ["format", "content"]
  }
  ```
- `execute()` 调用 `service.create_document()`，返回 `{"success": True, "filename": ..., "download_url": ..., "size": ...}`
- 在 `app.py` 中注册：`tool_registry.register(DocumentTool(service=document_service))`

**Patterns to follow:** `src/agentkit/tools/memory_tool.py`（Tool 基类模式、input_schema、execute 返回格式）。

**Test scenarios:**
- Happy path: format=word, content="# 标题\n段落" → 返回 success + download_url
- Happy path: format=pdf, content="..." → 返回 success + download_url
- Happy path: format=word + template + template_data → 模板填充成功
- Error path: format 无效 → 返回 success=False + error message
- Error path: content 为空 → 返回 success=False + error message
- Integration: 工具注册后 agent._tool_registry.get("document") 能获取到

**Verification:** 运行 `pytest tests/tools/test_document_tool.py`，确认工具注册和调用正常。

---

### U7. REST API 路由

**Goal:** 为前端提供文档处理的 REST API。

**Requirements:** R9, R10, R12, R27, R28

**Dependencies:** U1, U2, U3, U4, U5

**Files:**
- `src/agentkit/server/routes/documents.py`（新建）
- `src/agentkit/server/app.py`（修改：注册 documents router）
- `tests/routes/test_documents.py`（新建）

**Approach:**
- `router = APIRouter(prefix="/documents", tags=["documents"])`
- 端点：
  - `POST /api/v1/documents/create` — 创建文档（body: format, content, conversation_id, template?）
  - `POST /api/v1/documents/upload-template` — 上传模板文件（带认证）
  - `GET /api/v1/documents/conversation/{conversation_id}` — 获取对话的文档列表
  - `GET /api/v1/documents/download/{doc_id}` — 下载文档（带认证）
- 认证：复用 `Depends(_verify_api_key)` 模式
- 文件大小限制：50MB

**Patterns to follow:** `src/agentkit/server/routes/chat.py`（APIRouter 模式、文件上传/下载）、`src/agentkit/server/routes/kb_management.py`（认证模式）。

**Test scenarios:**
- Happy path: POST /create format=word → 200 + 文件元信息
- Happy path: GET /conversation/{id} → 200 + 文档列表
- Happy path: GET /download/{doc_id} → 200 + 文件流
- Security: 未认证请求 → 401
- Edge case: 不存在的 doc_id → 404
- Edge case: 文件超过 50MB → 413

**Verification:** 运行 `pytest tests/routes/test_documents.py`，用 curl 验证端点。

---

### U8. 前端文件卡片 + 右侧文档面板

**Goal:** 对话中渲染文件卡片，右侧面板展示当前对话的文档列表。

**Requirements:** R17, R18, R19, R20, R21, R22

**Dependencies:** U7

**Files:**
- `src/agentkit/server/frontend/src/components/chat/messages/DocumentCard.vue`（新建）
- `src/agentkit/server/frontend/src/components/chat/DocumentPanel.vue`（新建，右侧面板）
- `src/agentkit/server/frontend/src/stores/documents.ts`（新建，Pinia store）
- `src/agentkit/server/frontend/src/api/documents.ts`（新建，API client）
- `src/agentkit/server/frontend/src/views/ChatView.vue`（修改：集成右侧面板）
- `src/agentkit/server/frontend/src/stores/chat.ts`（修改：token 事件中检测文件元信息并更新 documents store）

**Approach:**
- `DocumentCard.vue`：复用 `FileAttachment.vue` 的设计，显示文件名、格式图标、大小、下载按钮。作为新的消息渲染类型。
- `DocumentPanel.vue`：右侧可折叠面板，展示当前对话的文档列表，每项显示文件名、格式图标、生成时间、下载链接。
- `stores/documents.ts`：`documentsByConversation: ref<Map<string, DocumentMeta[]>>`，`fetchDocuments(convId)`，`addDocument(convId, doc)`。
- `api/documents.ts`：`createDocument()`、`getConversationDocuments()`、`getDownloadUrl()`。
- ChatView 集成：在聊天区域右侧添加 DocumentPanel，根据当前 conversationId 加载文档列表。
- chat store 集成：当 Agent 工具返回文件元信息时，自动更新 documents store。

**Patterns to follow:** `src/agentkit/server/frontend/src/components/chat/messages/FileAttachment.vue`（组件模式）、`src/agentkit/server/frontend/src/stores/chat.ts`（Pinia store 模式）、`src/agentkit/server/frontend/src/api/client.ts`（API client 模式）。

**Test scenarios:**
- Happy path: Agent 生成文档后，对话中显示文件卡片
- Happy path: 右侧面板自动更新，显示新文档
- Happy path: 点击下载按钮，浏览器下载文件
- Happy path: 切换对话，面板显示对应对话的文档列表
- UI: 面板可折叠/展开
- Edge case: 对话无文档时，面板显示空状态

**Verification:** 启动前端开发服务器，手动测试文件卡片渲染和右侧面板交互。

---

### U9. 文档读取能力（复用 DocumentLoader）

**Goal:** Agent 能读取用户上传的 Word/Excel/PDF 文档内容。

**Requirements:** R4

**Dependencies:** U1

**Files:**
- `src/agentkit/tools/document_tool.py`（修改：添加 read 操作）
- `src/agentkit/memory/document_loader.py`（修改：确保 openpyxl 读取支持，或新增 Excel 读取）

**Approach:**
- DocumentTool 的 input_schema 新增 `action` 参数：`"create"` | `"read"`
- `action="read"` 时，调用 `DocumentLoader.load(path)` 读取文档内容
- DocumentLoader 已支持 PDF（PyMuPDF/pdfplumber）和 DOCX（python-docx），需新增 Excel 读取（openpyxl）
- 返回 `{"success": True, "content": "提取的文本内容"}`

**Patterns to follow:** `src/agentkit/memory/document_loader.py`（现有解析模式）。

**Test scenarios:**
- Happy path: 读取 .docx 文件，返回文本内容
- Happy path: 读取 .xlsx 文件，返回表格内容
- Happy path: 读取 .pdf 文件，返回文本内容
- Edge case: 空文件返回空字符串
- Error path: 不存在的文件返回 success=False

**Verification:** 运行 `pytest tests/tools/test_document_tool.py`，确认读取功能正常。

---

## Scope Boundaries

### Deferred to Follow-Up Work

- PPT 创建（.pptx）— v2
- 格式转换（Office→PDF）— v2，可能需要 LibreOffice
- PDF 合并和拆分 — v2
- Excel/PPT 模板填充 — v2
- 文档编辑 — v2
- MCP Document Tools 集成（可选增强）— v2
- 文档过期清理的定时任务实现 — v2（v1 手动清理或懒清理）

### Outside this product's identity

- OCR / 扫描文档识别
- 文档协作编辑
- 文档版本控制
- 云存储集成
- 文档水印 / 加密 / 数字签名

---

## Risks & Dependencies

- **Markdown→Office 格式映射的完整性** — Markdown 不能表达所有 Office 格式（如合并单元格、图片嵌入）。v1 只支持基本格式（标题、段落、列表、表格），复杂格式 defer。
- **中文字体在 PDF 中的渲染** — reportlab 默认不支持中文，需注册中文字体（如 SimSun 或 NotoSansCJK）。需确认服务器有中文字体文件。
- **python-docx-template 的 Jinja2 语法限制** — Office XML 结构中 Jinja2 语法可能受限（如表格内的循环）。需测试复杂模板。
- **前端右侧面板的布局影响** — 现有 ChatView 布局可能需要调整以容纳右侧面板，需确认不破坏现有聊天 UI。

---

## Sources & Research

- 需求文档：`docs/brainstorms/2026-06-23-document-processing-capability-requirements.md`
- Tool 基类：`src/agentkit/tools/base.py`、`src/agentkit/tools/memory_tool.py`
- ToolRegistry：`src/agentkit/tools/registry.py`、`src/agentkit/server/app.py`（第 239-269 行）
- 路由模式：`src/agentkit/server/routes/chat.py`、`src/agentkit/server/routes/kb_management.py`
- 数据库模式：`src/agentkit/server/auth/models.py`（aiosqlite 裸连接模式）
- 前端组件：`src/agentkit/server/frontend/src/components/chat/messages/FileAttachment.vue`
- 前端 store：`src/agentkit/server/frontend/src/stores/chat.ts`
- 文档解析：`src/agentkit/memory/document_loader.py`
- MCP Document Tools 验证报告：版本 0.1.0，未验证，不建议生产使用，不支持模板填充
