Metadata-Version: 2.4
Name: structured-writer-ldxs
Version: 1.1.0b0
Summary: structured-writer — AI Agent
Home-page: https://github.com/Ldxs001/workbuddy-skills
Author: Ldxs (wUwproject)
Author-email: wuwofc@yeah.net
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python
Dynamic: summary

# Structured Writer — 结构化写作智能体

基于 LLM 的结构化长文写作系统。子结构驱动、两级 RAG 增强、续写容断、交互式大纲控制。

## 核心架构

```
用户输入主题
  → [大纲规划器] LLM 生成结构化 JSON 大纲（节×子结构）
      → [交互式大纲] 用户可调整：勾选/排序/字数/重点/RAG
          → [串行写作器] 逐子结构调用 LLM 写作
              ├─ 节级别 RAG 查询（背景资料）
              ├─ 子结构级别 RAG 查询（针对性资料）
              ├─ 前文上下文注入（保连贯性）
              └─ token 耗尽自动续写
                  → 合并 .md 输出
```

## 特性

| 特性 | 说明 |
|------|------|
| **子结构系统** | 每节自动分解为 2-4 个子结构，逐子结构串行写作，`###` 标题分隔 |
| **两级 RAG** | 节级别查背景资料 + 子结构级别查针对性资料，prompt 分两段注入 |
| **续写机制** | 检测 `finish_reason="length"` 自动续写，最多 5 轮。空内容跳过续写 |
| **交互式大纲** | 勾选/取消节和子结构、阿拉伯数字排序、罗马数字子结构排序、字数编辑、重点标记 |
| **大纲双级排序** | 节：1-N；子结构：i-iv（每节独立） |
| **多模板** | 通用公文/新闻报道/论文综述/技术报告/自定义，切换即生效 |
| **实时进度** | 写作过程显示进度条 + 状态文本（RAG查询/写作中/完成） |
| **会话恢复** | 断线重连后恢复大纲和进度 |
| **RAG 冷启动** | 配置页一键启动 rag-assistant 子进程，自动检测在线状态 |

## 快速开始

```bash
pip install structured-writer-ldxs
structured-writer-ldxs --port 8770
```

打开 http://localhost:8770

## 配置

配置 Tab 设置写作者模型（LM Studio / Ollama）、规划者模型、上下文窗口、提示词模板。

### 模型推荐

| 角色 | 推荐模型 | 注意事项 |
|------|---------|---------|
| 写作者 | Qwen3.5-35b-A3B / 同级别 | 推理模型 max_tokens 建议 ≥8192 |
| 规划者 | Qwen3.5-35b-A3B / 同级别 | 大纲生成需要语义理解能力 |

## RAG 对接

本系统依赖 [rag-assistant](https://github.com/Ldxs001/workbuddy-skills/tree/main/agent/rag-assistant) 的知识库查询能力：

1. 启动 rag-assistant（或通过配置页一键冷启动）
2. 配置页填入 rag-assistant 路径，点击冷启动
3. 大纲中勾选 RAG + 选择知识库
4. 系统自动做两级 RAG 查询：节背景 + 子结构针对性

## 高级用法

### 大纲勾选

取消勾选的节/子结构在生成时完全跳过，不写标题也不占字数。

### 续写

LLM 输出被 `max_tokens` 截断时自动追加"请继续写"指令重试。content 为空（推理吃光 token）时放弃续写，不卡死。

## PyPI

```bash
pip install structured-writer-ldxs
```

## 许可证

Apache 2.0 © wUwproject


---

## 更新说明

## [1.1.0b0] - 2026-07-28
### 新增
- **五元组结构化模板系统**：模板从纯文本提示词升级为 `{name, show_label, desc, source, type}` 五元组结构，一份数据结构同时定义元数据（标题/作者/单位等）和内容树（引言/正文/结论/参考文献等），覆盖日常写作/学术论文/正式公文/新闻报道/技术报告全部类型
- **动态 Planner prompt 生成**：`plan_outline()` 根据五元组按 `source=user/llm/auto` 分类处理，user 字段不碰、llm 字段必生成、auto 字段用户可填留空 LLM 兜底
- **type:leaf 节支持**：无子结构的扁平节（标题/关键词/摘要/参考文献等），渲染跳过 `###` 标题，直接写内容在 `##` 下
- **meta 块输出**：文章全文前插入 `> 名称：值` 元数据块，按 `show_label` 控制前缀显隐
- **结构表格编辑器**：配置 tab 新增五列可编辑表格（名称/显示/字段意义/填写者/子结构类型）+ 纯展示"渲染"列（自动推导字段出现在聊天输入框还是大纲节）
- **字段意义模态框**：点击表格行中的"字段意义"预览文字弹出 modal textarea，支持长文本编辑，表格中显示截断预览
- **LLM 对话生成模板**：配置 tab "从对话生成" 按钮 → 弹窗输入描述 + 可选模板名称 → LLM 自动生成五元组结构 + 风格提示词 → 保存为自定义模板
- **动态 meta 输入框**：根据模板 `source=user/auto` 的字段，在聊天气泡下方按 4 列 grid 动态渲染输入框，值自动传给 Planner
- **模板搜索排序**：下拉框按拼音字母排序，"自定义"永远在最后
- **内置模板元数据字段**：学术论文/正式公文等新模板预置作者/单位/文号/关键词等字段
- **模板选择持久化**：切换模板时自动保存 `selected_template` 到 config.json，重启后恢复
- **ThreadingHTTPServer**：从单线程 `HTTPServer` 升级为多线程，LLM 请求不阻塞其他 API（归档/配置/进度）
- **删除会话双击确认**：归档会话的删除按钮，第一次单击变红显示"确认?"，2.5 秒内再点执行删除，替代 `confirm()` 浏览器弹窗
- **favicon 静默处理**：返回 `204 No Content`，消除控制台 404

### 变更
- `config.json` 模板格式重构：`templates` 从 `{"名": "字符串"}` 升级为 `{"名": {"structure": [五元组], "style": "字符串"}}`
- `planner.py` 接口变更：`plan_outline()` 新增 `template` 和 `user_meta` 参数，旧字符串调用兼容
- `writer.py` 接口变更：`generate_article()` 新增 `template` 参数（用于 meta 渲染），默认 `None` 兼容旧调用
- `web_ui.py` 路由表新增 `/api/gen-template` 和 `/favicon.ico`
- `config_manager.py` 新增旧格式自动迁移 + "自定义"模板硬保护

### 修复
- `const label` 重复声明导致 JS 加载失败 → 删掉重复行
- 从对话生成模板 `max_tokens=4096` 导致 JSON 截断 → 改为 `None`（走配置值）加 3 次重试 + 多级 JSON 容错解析
- `HTTPServer` 单线程阻塞 UI → 替换为 `ThreadingHTTPServer`
- `onTemplateChange()` 未持久化 `selected_template` → 切换时自动保存
- 模板下拉框排序混乱 → 拼音字母排序 + "自定义"永远最后
- 旧纯字符串模板格式迁移 → `config_manager.py` `load()` 自动检测+转换
### 新增
- **每子结构字数可编辑**：章节字数改为子结构字数之和（自动实时求和），子结构字数输入框直接可改；取消勾选的子结构不计入章节字数
- **进度条按过滤后子结构总数计算**：取消勾选的子结构不再计入进度分母
- **RAG 离线时复选框禁用**：8767 未上线时 RAG 复选框 disabled＋title 提示；上线后自动同步 KB 下拉框
- **子结构辅助知识模态框**：每个子结构 "+" 按钮 → 弹窗支持文本输入 + .txt/.md 文件上传（FileReader 前端读取）
- **RAG 与辅助知识 Prompt 分离注入**：`【RAG 参考资料】` 和 `【辅助知识】` 两段独立标注
- **前文回顾字数可配置**：配置页 "写作参数" 新增输入框，`context_review_length` 写入 config.json
- **配置项自动合并**：`config_manager.load()` 深层合并（嵌套 dict 中新 key 自动补上）；`update()` 支持写入新增键
- **LLM 模型自动检测**：`_build_payload` 中 model 为空时自动调 `list_models()` 取第一个已加载模型
- **批量自动撰写**：输入框写入多行（每行一个主题）→ 后端 `/api/batch_auto` 逐篇规划+RAG+生成 → 前端轮询批量进度
- **单篇自动撰写**：输入框旁 "自动撰写" 按钮 → 前端 chain `plan→generate`，全量自动 RAG
- **事实自检系统**：配置页 "事实自检" 开关 → 写作 prompt 末尾内嵌 `【事实待核查】` 标记 → LLM 在同一 response 中自检 → 解析标记收集 → 文章末尾编号列表汇总。**零额外 LLM 调用**
- **无问题时也输出自检段落**：即使所有子结构都返回"无"，文章末尾也输出 `## 建议人工复审` + `未发现需标记的问题`
- **会话归档/恢复/删除**：侧边栏每项 "🗂 归档" 按钮 → `data/archives/sessions/` 折叠区 → "↩ 恢复" + "✕ 删除"（`confirm()` 确认）
- **自动会话限额**：`max_sessions`（默认 20）→ 新建会话超出时自动归档最旧非当前会话
- **停止生成**：聊天区底部 "延时停止"（当前子结构写完停）+ "立即停止"（续写边界停）→ 保留已写内容输出 .md
- **规划器优先遵循用户指令**：约束前加 "优先遵循用户明确指定的结构要求"，`sections 数量` 改为 "如用户未指定"
- **规划/写作模型温度可配置**：配置页新增 "温度" 输入框（0-1，step=0.05），规划默认 0.6、写作默认 0.7，持久化到 config.json
- **LLM 客户端 temperature 参数**：`LLMClient.__init__` 加 `temperature`，`chat`/`chat_detailed`/`_build_payload` 默认值改为 `None`（走 `self.temperature`）
- **模型下拉框始终显示已保存的模型**：`refreshModels` 接受 `savedValue` 参数，配置模型不在 API 返回列表时追加 `xxx（已配置）` option
- **RAG 停止按钮**：配置页新增 "停止 RAG" 按钮 → 后端 `_handle_rag_stop` → `taskkill /F /T` 杀进程树 + `netstat` 查 8767 + 等端口释放 + auto-restart 检测
- **RAG 停止后不再显示"运行中"**：`_ragManuallyStopped` 标记阻止轮询跳回运行中状态，直到用户手动点击"冷启动 RAG"
- **RAG 状态轮询加速**：cache-buster 防缓存，间隔 3s→1.5s，启动后立即查一次

### 变更
- **自检从额外 LLM 调用改为内嵌标记**：删除 `FACT_CHECK_PROMPT` 和独立 `SELF_CHECK_SYSTEM_PROMPT`，改为在写作 prompt 末尾追加 `【事实待核查】` 标注要求，response 里直接解析
- **规划器 `max_tokens` 从配置读**：删除硬编码 4096，改用 `max(4096, llm_client.max_tokens)`
- **写作器/规划器 LLM 客户端统一工厂**：`_create_writer_client()` / `_create_planner_client()` 传 `temperature`
- **Planner/writer temperature 硬编码删除**：`planner.py` `temperature=0.6` → `None`；`writer.py` `temperature=0.7` → `None`（走客户端配置）
- **`status_text` 仅 writing 阶段返回**：`get_progress()` 非 writing 阶段返回空字符串，防止加载旧会话显示脏数据
- **状态文本生成时自动清空**：`_handle_generate` 入口调用 `set_status_text("")`
- **配置页提示文案更新**：改为 "推理模型建议不低于 4096（默认最低值），长文建议 8192 以上"

### 修复
- `planner.py` 硬编码 `max_tokens=4096` 导致推理模型 thinking 吃掉全部 token → JSON 输出为空
- `config_manager.py` `update()` 无法写入新增配置键 → `fact_check_enabled` 等不持久化
- `config_manager.py` `load()` 不合并 DEFAULT_CONFIG 缺失项 → 旧 config.json 没有新字段
- 自检 `max_tokens` 各值（2048/8192/512）导致推理模型 thinking 吃光 → 改为 `None`（走配置的 81920）
- 自检使用独立 system prompt → LLM 混淆角色 → 改为共享 `WRITER_SYSTEM_PROMPT`
- 自检额外 LLM 调用导致额外 token 消耗 → 改为内嵌标记法，零额外调用
- 加载旧会话时 `_status_text` 脏数据被轮询读出并显示
- 章节字数 input 可编辑但子结构字数不变 → 数据不一致
- 子结构取消勾选后章节字数不减 → 重算函数忽略未勾选
- 模型下拉框加载时显示"(请选择)"而非已保存模型 → `refreshModels` 接受 `savedValue` 回退
- RAG 冷启动后无法关闭 → 新增停止按钮 + 后端进程树 kill + 端口释放等待
- RAG 停止后轮询仍跳回"运行中" → `_ragManuallyStopped` 标记保护
- RAG 状态检测被浏览器缓存 → 加 `?_=Date.now()` cache-buster

---
