feat: 专员 JSON 配置体系 + 配图提示词规则重构
- 新增配图提示词 JSON 规则(image_prompt.json):封面图 + 每 500 字按字数分布 - 新增 Go 配置加载器(weixin_public_account_image_prompt_config.go):从嵌入的 spec 目录读取 JSON 配置 - 新增 weixin_public_account 完整 spec 目录:skill.md, workflow.json, domains.json, policy.json, validators/ - 新增 pj0012 参考文件(pj0012-wechat-image-spec.md):配图规则 + 专员 JSON 定义体系 - 产物 Tab 添加下载按钮 + 整体导出区 - 修复前端 Markdown 渲染 bug + 步骤消息分段问题 所有专员定义向 JSON 配置迁移,以 weixin_public_account 为样板。
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# ================================================================
|
||||
# EAI AgentPlatform 环境变量配置
|
||||
# ================================================================
|
||||
# 复制此文件为 .env 并填入实际值:
|
||||
# cp .env.example .env
|
||||
# ================================================================
|
||||
|
||||
# ---------- 后端服务 ----------
|
||||
# 后端 API 端口
|
||||
PORT=8080
|
||||
|
||||
# SQLite 数据库路径
|
||||
DB_PATH=./data/eai_agentplatform.db
|
||||
|
||||
# JWT 密钥(生产环境请修改)
|
||||
JWT_SECRET=change-this-to-a-strong-secret-in-production
|
||||
JWT_EXPIRE_MINUTES=480
|
||||
|
||||
# ---------- AI 服务 ----------
|
||||
# LLM 服务地址(llamacpp 默认 8080)
|
||||
LLM_BASE_URL=http://127.0.0.1:8080/v1
|
||||
LLM_API_KEY=local
|
||||
LLM_MODEL=Qwen3.5-4B-Q4_K_M.gguf
|
||||
|
||||
# 向量嵌入模型
|
||||
EMBED_MODEL=bge-m3
|
||||
|
||||
# ---------- 知识库 ----------
|
||||
KB_DATA_DIR=./data/kb_data
|
||||
KNOWLEDGE_SOURCE_DIR=./assets/knowledge/source
|
||||
TRAINING_MATERIALS_DIR=./assets/training/materials
|
||||
|
||||
# ---------- 备份 ----------
|
||||
BACKUP_ENABLED=true
|
||||
BACKUP_DIR=./data/backups
|
||||
BACKUP_KEEP=7
|
||||
BACKUP_INTERVAL_HOURS=24
|
||||
|
||||
# ---------- 文件上传限制 ----------
|
||||
FILE_MAX_SIZE_DOC=209715200
|
||||
FILE_MAX_SIZE_VIDEO=2147483648
|
||||
CHUNK_THRESHOLD=104857600
|
||||
|
||||
# ---------- 文档转换 ----------
|
||||
LIBREOFFICE_BIN=libreoffice
|
||||
PDFTOTEXT_BIN=pdftotext
|
||||
|
||||
# ---------- 金蝶集成 ----------
|
||||
# KINGDEE_BASE_URL=
|
||||
# KINGDEE_ACCOUNT_ID=
|
||||
# KINGDEE_USERNAME=
|
||||
# KINGDEE_PASSWORD=
|
||||
# KINGDEE_LCID=2052
|
||||
# KINGDEE_TIMEOUT_SEC=15
|
||||
@@ -0,0 +1,52 @@
|
||||
# EAI AgentPlatform
|
||||
|
||||
智能 Agent 平台 — 基于 Go + Vue 3 构建。
|
||||
|
||||
## 快速启动
|
||||
|
||||
```bash
|
||||
# 开发环境启动(前端 10231 + 后端 10232)
|
||||
./start_dev_10231_10232.sh
|
||||
```
|
||||
|
||||
## 端口说明
|
||||
|
||||
| 端口 | 服务 | 说明 |
|
||||
|--------|--------------------------------|----------------------------|
|
||||
| 10231 | 前端 Vite 开发服务器 | 本地开发,自动代理 API 请求 |
|
||||
| 10232 | 后端 Go API 服务 | 本地开发端口 |
|
||||
| 8080 | 外部 LLM 服务 (llamacpp) | AI 对话 API 入口 |
|
||||
| 8081 | 外部 Embed 服务 (llamacpp) | 向量嵌入服务 |
|
||||
| 11434 | 外部 Ollama 服务(可选) | 如使用 Ollama 路由则需运行 |
|
||||
|
||||
> **注意**: 8080 和 8081 是外部 AI 服务的端口,平台后端会调用它们获取 LLM 对话和向量嵌入能力。
|
||||
|
||||
## 配置
|
||||
|
||||
复制环境变量模板并修改:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
主要配置项见 [.env.example](.env.example)。
|
||||
|
||||
## 启动脚本参数
|
||||
|
||||
```
|
||||
--force 强制释放端口(包括非本项目进程)
|
||||
--skip-preflight 跳过预检查
|
||||
--skip-smoke 跳过管理员登录冒烟测试
|
||||
--skip-build 不重新编译后端
|
||||
--help 显示帮助
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|-------------------------|-----------------------------------------|-----------------------|
|
||||
| PORT | 8080 | 后端 API 端口 |
|
||||
| EAI_DEV_BIND_HOST | 127.0.0.1 | 开发绑定地址 |
|
||||
| EAI_DEV_PUBLIC_HOST | 自动检测 | 公开访问地址 |
|
||||
| LLM_BASE_URL | http://127.0.0.1:8080/v1 | LLM 服务地址 |
|
||||
| EMBED_MODEL | bge-m3 | 向量嵌入模型名 |
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"description": "业务域配置。每个业务域有对应的标签和默认配置。",
|
||||
"version": "1.0.0",
|
||||
"domains": {
|
||||
"AI": {
|
||||
"label": "AI",
|
||||
"image_domain_label": "AI 与人工智能",
|
||||
"hotspot_sources": ["hackernews", "techcrunch", "arxiv"],
|
||||
"default_keywords": ["大语言模型", "AI Agent", "多模态", "AIGC"]
|
||||
},
|
||||
"medical": {
|
||||
"label": "医疗",
|
||||
"image_domain_label": "医疗健康",
|
||||
"hotspot_sources": ["medical_news_today", "statnews"],
|
||||
"default_keywords": ["新药研发", "临床实验", "健康管理", "医疗AI"]
|
||||
},
|
||||
"insurance": {
|
||||
"label": "保险",
|
||||
"image_domain_label": "保险金融",
|
||||
"hotspot_sources": ["reuters", "bloomberg"],
|
||||
"default_keywords": ["健康险", "车险", "理赔", "保险科技"]
|
||||
},
|
||||
"hr": {
|
||||
"label": "HR",
|
||||
"image_domain_label": "人力资源",
|
||||
"hotspot_sources": ["linkedin", "shrm"],
|
||||
"default_keywords": ["招聘", "培训", "绩效考核", "薪酬福利"]
|
||||
},
|
||||
"legal": {
|
||||
"label": "法务",
|
||||
"image_domain_label": "法律合规",
|
||||
"hotspot_sources": ["reuters", "bloomberg"],
|
||||
"default_keywords": ["数据合规", "知识产权", "劳动法", "合同法"]
|
||||
},
|
||||
"general": {
|
||||
"label": "通用",
|
||||
"image_domain_label": "通用主题",
|
||||
"hotspot_sources": ["google_trending", "reddit"],
|
||||
"default_keywords": []
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"description": "配图提示词生成规则。配图按字数均匀分布,封面图在标题下方,其余配图按 500 字间隔放置在所属小节开始处。",
|
||||
"version": "1.0.0",
|
||||
"distribution": {
|
||||
"mode": "word_count",
|
||||
"word_count_threshold": 500,
|
||||
"cover_image": {
|
||||
"enabled": true,
|
||||
"key": "cover",
|
||||
"slot_label": "TITLE_PIC",
|
||||
"section_label": "封面配图",
|
||||
"position": "after_title",
|
||||
"size": "landscape_16_9"
|
||||
},
|
||||
"content_images": {
|
||||
"enabled": true,
|
||||
"key_prefix": "section",
|
||||
"size": "landscape_4_3",
|
||||
"placement": "section_start",
|
||||
"calculation": "accumulate_across_sections"
|
||||
}
|
||||
},
|
||||
"prompt_template": {
|
||||
"description": "配图提示词模板。{index} 为序号(从 1 开始),{section_title} 为小节标题,{section_summary} 为段落摘要(截断至 120 字)。",
|
||||
"format": "chinese",
|
||||
"min_chars": 40,
|
||||
"max_chars": 70,
|
||||
"template": "真实人物纪实摄影,公众号文章配图,第{index}张配图核心画面,{section_summary},构图清晰,主体突出,电影感光影,细节丰富,高清"
|
||||
},
|
||||
"bracket_format": {
|
||||
"description": "配图提示词在正文中的嵌入格式。",
|
||||
"cover": "[TITLE_PIC: {prompt}, size: {size}]",
|
||||
"content": "[section_{{index:02d}}: {prompt}, size: {size}]"
|
||||
},
|
||||
"validation": {
|
||||
"checks": [
|
||||
"section_mapping_completeness",
|
||||
"required_visual_elements",
|
||||
"prompt_length",
|
||||
"no_forbidden_elements"
|
||||
],
|
||||
"required_elements": {
|
||||
"subject": true,
|
||||
"scene": true,
|
||||
"lighting": true
|
||||
},
|
||||
"forbidden_elements": [
|
||||
"text_overlay",
|
||||
"collage_style"
|
||||
],
|
||||
"retry_max": 2,
|
||||
"retry_feedback_template": "配图提示词校验失败:{failed_checks}。请重新生成全部提示词并修复问题,保持段落映射不变。"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"description": "重试和降级策略。",
|
||||
"version": "1.0.0",
|
||||
"retries": {
|
||||
"default_max_retries": 2,
|
||||
"step_overrides": {
|
||||
"topic_recommendation": 1,
|
||||
"title_generation": 2,
|
||||
"outline_generation": 2,
|
||||
"content_creation": 2,
|
||||
"image_prompt_generation": 2,
|
||||
"image_generation": 2,
|
||||
"preview_export": 0,
|
||||
"page_markdown": 0
|
||||
},
|
||||
"policy": {
|
||||
"regenerate_fully": true,
|
||||
"include_error_message_in_feedback": true,
|
||||
"allow_partial_patch": false
|
||||
}
|
||||
},
|
||||
"fallback": {
|
||||
"mark_fallback_in_logs": true,
|
||||
"step_overrides": {
|
||||
"topic_recommendation": {
|
||||
"allow_fallback": true,
|
||||
"fallback_note": "使用关键词直接生成选题。"
|
||||
},
|
||||
"title_generation": {
|
||||
"allow_fallback": true,
|
||||
"fallback_note": "使用标题模板生成。"
|
||||
},
|
||||
"content_creation": {
|
||||
"allow_fallback": false,
|
||||
"fallback_note": "不降级,返回明确错误。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Role
|
||||
你是 `image_prompt_generate` 步骤的执行者,负责为公众号文章生成配图提示词。
|
||||
|
||||
# Goal
|
||||
基于正文内容,按字数分布规则生成配图提示词,并将提示词以括号标记格式嵌入正文。
|
||||
|
||||
# 配图规则
|
||||
- 封面图:标题下方 1 张,`[TITLE_PIC: 提示词, size: landscape_16_9]`
|
||||
- 正文配图:从正文开头累计字数,每 500 字生成 1 张
|
||||
- 每张配图放在所属小节(##)的开始处
|
||||
- 如果某节超过 500 字,则该节内按 500 字间隔插入多张
|
||||
- 提示词格式:`[section_{{index:02d}}: 提示词, size: landscape_4_3]`
|
||||
|
||||
# 提示词模板
|
||||
```
|
||||
真实人物纪实摄影,公众号文章配图,第{N}张配图核心画面,{段落摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清
|
||||
```
|
||||
|
||||
# 约束
|
||||
- 每个提示词必须包含:主体、场景、光线
|
||||
- 每个提示词长度 40-70 个中文字
|
||||
- 禁止文字覆盖、拼贴风格
|
||||
- 段落映射必须完整(配图数 = 需要配图的小节数)
|
||||
|
||||
# 输出
|
||||
- `image_prompts`: 配图提示词数组 [{key, section, prompt, size}]
|
||||
- `content_with_prompts`: 带括号标记的完整正文
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# 公众号创作专员技能规范
|
||||
|
||||
## 技能标识
|
||||
- **Key**: weixin-public-account
|
||||
- **Label**: 公众号创作专员
|
||||
- **Type**: longform-writing
|
||||
|
||||
## 目的
|
||||
完成公众号文章的端到端创作:从选题、标题、提纲、正文、配图提示词、图片生成、预览导出到分页 Markdown。
|
||||
|
||||
## 适用场景
|
||||
- 热点选题驱动的内容创作
|
||||
- 关键词驱动的通用文章生成
|
||||
- 多业务域(AI/医疗/保险/HR/法务/通用)适配
|
||||
|
||||
## 工作流步骤
|
||||
|
||||
| 序号 | Step Key | 标签 | 前置条件 | 输出 |
|
||||
|------|----------|------|----------|------|
|
||||
| 0 | task_configuration | 任务配置 | — | 业务域、关键词、受众、语调等表单参数 |
|
||||
| 1 | topic_recommendation | 选题推荐 | 配置完成 | 3 个选题建议(标题 + 理由 + 热度) |
|
||||
| 2 | title_generation | 标题生成 | 有选题 | 3 个标题建议(偏点击/偏稳重/偏平衡) |
|
||||
| 3 | outline_generation | 提纲生成 | 有标题 | Markdown 提纲结构 |
|
||||
| 4 | content_creation | 正文创作 | 有提纲 | Markdown 正文(含 ## 小节标题) |
|
||||
| 5 | image_prompt_generation | 配图提示词 | 有正文 | 配图提示词 + 带标记正文 |
|
||||
| 6 | image_generation | 图片生成 | 有配图提示词 | 封面图 + 段落图 |
|
||||
| 7 | preview_export | 预览导出 | 有正文 + 图片 | 预览 MD + 预览 HTML |
|
||||
| 8 | page_markdown | 分页 Markdown | 有预览稿 | 分页 MD(含图片尺寸标注) |
|
||||
|
||||
## 配图规则
|
||||
|
||||
### 分布规则
|
||||
1. 标题下方生成 1 张封面图(`cover` / `TITLE_PIC`),16:9
|
||||
2. 从正文开头累计字数,每 500 字生成 1 张配图
|
||||
3. 每张配图放在其所属小节(##)的**开始处**
|
||||
4. 如果某节很长(>500 字),则该节内按 500 字间隔插入多张
|
||||
5. 如果某节不足 500 字,与下节合并累计,但不跨节重复
|
||||
|
||||
### 提示词模板
|
||||
```
|
||||
真实人物纪实摄影,公众号文章配图,第{N}张配图核心画面,{段落摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清
|
||||
```
|
||||
|
||||
### 括号标记格式
|
||||
```
|
||||
[TITLE_PIC: 提示词, size: landscape_16_9]
|
||||
[section_01: 提示词, size: landscape_4_3]
|
||||
```
|
||||
|
||||
## 业务域
|
||||
AI、医疗、保险、HR、法务、通用
|
||||
|
||||
## 重试策略
|
||||
- 热点抓取:最多 1 次重试
|
||||
- 选题推荐:最多 1 次重试
|
||||
- 标题/提纲/正文/配图:最多 2 次重试
|
||||
- 预览/分页:0 次重试
|
||||
|
||||
## 验证规则
|
||||
- 提纲:至少 3 个小节,每小节至少 2 个要点
|
||||
- 正文:至少 800 字,每个小节有实质性内容
|
||||
- 配图提示词:段落数 = 提示词数,每个提示词 40-70 字,必须包含主体/场景/光线
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"description": "配图提示词验证规则。",
|
||||
"version": "1.0.0",
|
||||
"step_id": "image_prompt_generation",
|
||||
"checks": [
|
||||
"section_mapping_completeness",
|
||||
"required_visual_elements",
|
||||
"prompt_length",
|
||||
"no_forbidden_elements",
|
||||
"bracket_format_valid"
|
||||
],
|
||||
"section_mapping": {
|
||||
"description": "配图提示词与正文段落的映射必须完整。",
|
||||
"cover_required": true,
|
||||
"content_images_required": true
|
||||
},
|
||||
"visual_elements": {
|
||||
"required": ["subject", "scene", "lighting"],
|
||||
"forbidden": ["text_overlay", "collage_style"]
|
||||
},
|
||||
"prompt_length": {
|
||||
"min_chars": 40,
|
||||
"max_chars": 70,
|
||||
"count_mode": "chinese_characters"
|
||||
},
|
||||
"bracket_format": {
|
||||
"cover_pattern": "^\\[TITLE_PIC: .* size: landscape_16_9\\]$",
|
||||
"content_pattern": "^\\[section_\\d{2}: .* size: landscape_4_3\\]$"
|
||||
},
|
||||
"retry_feedback_template": "配图提示词校验失败:{failed_checks}。请重新生成全部提示词并修复问题。",
|
||||
"retry_max": 2
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"description": "公众号创作工作流步骤定义。步骤按顺序执行,前一步骤完成方可进入下一步骤。",
|
||||
"version": "1.0.0",
|
||||
"steps": [
|
||||
{
|
||||
"key": "task_configuration",
|
||||
"label": "任务配置",
|
||||
"type": "form",
|
||||
"order": 0,
|
||||
"is_entry_point": true,
|
||||
"description": "配置业务域、关键词、受众、语调等参数。"
|
||||
},
|
||||
{
|
||||
"key": "topic_recommendation",
|
||||
"label": "选题推荐",
|
||||
"type": "ai",
|
||||
"order": 1,
|
||||
"preconditions": ["task_configuration"],
|
||||
"description": "基于业务域和关键词生成 3 个选题建议。"
|
||||
},
|
||||
{
|
||||
"key": "title_generation",
|
||||
"label": "标题生成",
|
||||
"type": "ai",
|
||||
"order": 2,
|
||||
"preconditions": ["topic_recommendation"],
|
||||
"description": "基于选定选题生成 3 个标题建议(偏点击/偏稳重/偏平衡)。"
|
||||
},
|
||||
{
|
||||
"key": "outline_generation",
|
||||
"label": "提纲生成",
|
||||
"type": "ai",
|
||||
"order": 3,
|
||||
"preconditions": ["title_generation"],
|
||||
"description": "基于选定标题生成 Markdown 提纲结构。"
|
||||
},
|
||||
{
|
||||
"key": "content_creation",
|
||||
"label": "正文创作",
|
||||
"type": "ai",
|
||||
"order": 4,
|
||||
"preconditions": ["outline_generation"],
|
||||
"description": "基于选定提纲和配置参数生成正文。"
|
||||
},
|
||||
{
|
||||
"key": "image_prompt_generation",
|
||||
"label": "配图提示词",
|
||||
"type": "ai",
|
||||
"order": 5,
|
||||
"preconditions": ["content_creation"],
|
||||
"description": "基于正文按字数分布生成配图提示词,并嵌入带标记正文。"
|
||||
},
|
||||
{
|
||||
"key": "image_generation",
|
||||
"label": "图片生成",
|
||||
"type": "external",
|
||||
"order": 6,
|
||||
"preconditions": ["image_prompt_generation"],
|
||||
"description": "基于配图提示词调用外部图片生成 API。"
|
||||
},
|
||||
{
|
||||
"key": "preview_export",
|
||||
"label": "预览导出",
|
||||
"type": "render",
|
||||
"order": 7,
|
||||
"preconditions": ["content_creation", "image_generation"],
|
||||
"description": "生成预览 MD、预览 HTML 和分页 MD。"
|
||||
},
|
||||
{
|
||||
"key": "page_markdown",
|
||||
"label": "分页 Markdown",
|
||||
"type": "render",
|
||||
"order": 8,
|
||||
"preconditions": ["preview_export"],
|
||||
"description": "生成带图片尺寸标注的分页 Markdown。"
|
||||
}
|
||||
],
|
||||
"forms": {
|
||||
"task_configuration": {
|
||||
"fields": [
|
||||
{
|
||||
"key": "business_domain",
|
||||
"label": "业务域",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": ["AI", "医疗", "保险", "HR", "法务", "通用"]
|
||||
},
|
||||
{
|
||||
"key": "keyword",
|
||||
"label": "关键词",
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "audience",
|
||||
"label": "目标受众",
|
||||
"type": "text",
|
||||
"default": "公众号读者",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "goal",
|
||||
"label": "写作目标",
|
||||
"type": "text",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "tone",
|
||||
"label": "语调风格",
|
||||
"type": "select",
|
||||
"default": "专业但好懂",
|
||||
"options": ["专业但好懂", "轻松有趣", "严肃权威", "故事叙述", "数据驱动"]
|
||||
},
|
||||
{
|
||||
"key": "requirements",
|
||||
"label": "特殊要求",
|
||||
"type": "textarea",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "outline_style",
|
||||
"label": "提纲风格",
|
||||
"type": "select",
|
||||
"default": "问题拆解型",
|
||||
"options": ["问题拆解型", "对比分析型", "案例驱动型", "步骤教程型"]
|
||||
},
|
||||
{
|
||||
"key": "outline_words",
|
||||
"label": "提纲字数",
|
||||
"type": "number",
|
||||
"default": 800,
|
||||
"min": 300,
|
||||
"max": 2000,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "content_target_words",
|
||||
"label": "正文目标字数",
|
||||
"type": "number",
|
||||
"default": 1400,
|
||||
"min": 500,
|
||||
"max": 5000,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "max_content_images",
|
||||
"label": "最大配图数(可选,0 表示无上限)",
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 20,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
package weixinpublicaccountapi
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed spec/config/*.json spec/config/validators/*.json spec/config/prompts/*.md
|
||||
var specConfigFS embed.FS
|
||||
|
||||
// imagePromptConfig 从 JSON 配置文件读取的配图提示词规则。
|
||||
type imagePromptConfig struct {
|
||||
Distribution distributionConfig `json:"distribution"`
|
||||
PromptTemplate promptTemplateConfig `json:"prompt_template"`
|
||||
BracketFormat bracketFormatConfig `json:"bracket_format"`
|
||||
Validation validationConfig `json:"validation"`
|
||||
}
|
||||
|
||||
type distributionConfig struct {
|
||||
Mode string `json:"mode"`
|
||||
WordCount int `json:"word_count_threshold"`
|
||||
CoverImage coverImageConfig `json:"cover_image"`
|
||||
ContentImages contentImagesConfig `json:"content_images"`
|
||||
}
|
||||
|
||||
type coverImageConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Key string `json:"key"`
|
||||
SlotLabel string `json:"slot_label"`
|
||||
SectionLbl string `json:"section_label"`
|
||||
Position string `json:"position"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
type contentImagesConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
Size string `json:"size"`
|
||||
Placement string `json:"placement"`
|
||||
Calculation string `json:"calculation"`
|
||||
}
|
||||
|
||||
type promptTemplateConfig struct {
|
||||
Format string `json:"format"`
|
||||
MinChars int `json:"min_chars"`
|
||||
MaxChars int `json:"max_chars"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
type bracketFormatConfig struct {
|
||||
Cover string `json:"cover"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type validationConfig struct {
|
||||
Checks []string `json:"checks"`
|
||||
RequiredElements map[string]bool `json:"required_elements"`
|
||||
ForbiddenElements []string `json:"forbidden_elements"`
|
||||
RetryMax int `json:"retry_max"`
|
||||
RetryFeedbackTemplate string `json:"retry_feedback_template"`
|
||||
}
|
||||
|
||||
// imagePromptRule 是运行时的实例化规则(含业务域标签、关键词等运行时参数)。
|
||||
type imagePromptRule struct {
|
||||
Keyword string
|
||||
BusinessDomain string
|
||||
Template promptTemplateConfig
|
||||
Distribution distributionConfig
|
||||
BracketFormat bracketFormatConfig
|
||||
}
|
||||
|
||||
var weixinPublicAccountImagePromptConfig *imagePromptConfig
|
||||
var weixinPublicAccountImagePromptRuleCache map[string]*imagePromptRule
|
||||
|
||||
func init() {
|
||||
weixinPublicAccountImagePromptConfig = loadWeixinPublicAccountImagePromptConfig()
|
||||
weixinPublicAccountImagePromptRuleCache = make(map[string]*imagePromptRule)
|
||||
}
|
||||
|
||||
// loadWeixinPublicAccountImagePromptConfig 从嵌入的 spec 目录加载 JSON 配置。
|
||||
func loadWeixinPublicAccountImagePromptConfig() *imagePromptConfig {
|
||||
data, err := specConfigFS.ReadFile("spec/config/image_prompt.json")
|
||||
if err != nil {
|
||||
// 如果 spec 目录不存在或配置缺失,回退到内联默认配置
|
||||
return defaultImagePromptConfig()
|
||||
}
|
||||
var cfg imagePromptConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return defaultImagePromptConfig()
|
||||
}
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func defaultImagePromptConfig() *imagePromptConfig {
|
||||
return &imagePromptConfig{
|
||||
Distribution: distributionConfig{
|
||||
Mode: "word_count",
|
||||
WordCount: 500,
|
||||
CoverImage: coverImageConfig{
|
||||
Enabled: true,
|
||||
Key: "cover",
|
||||
SlotLabel: "TITLE_PIC",
|
||||
SectionLbl: "封面配图",
|
||||
Position: "after_title",
|
||||
Size: "landscape_16_9",
|
||||
},
|
||||
ContentImages: contentImagesConfig{
|
||||
Enabled: true,
|
||||
KeyPrefix: "section",
|
||||
Size: "landscape_4_3",
|
||||
Placement: "section_start",
|
||||
Calculation: "accumulate_across_sections",
|
||||
},
|
||||
},
|
||||
PromptTemplate: promptTemplateConfig{
|
||||
Format: "chinese",
|
||||
MinChars: 40,
|
||||
MaxChars: 70,
|
||||
Template: "真实人物纪实摄影,公众号文章配图,第{N}张配图核心画面,{段落摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清",
|
||||
},
|
||||
BracketFormat: bracketFormatConfig{
|
||||
Cover: "[TITLE_PIC: {prompt}, size: {size}]",
|
||||
Content: "[section_{{index:02d}}: {prompt}, size: {size}]",
|
||||
},
|
||||
Validation: validationConfig{
|
||||
Checks: []string{
|
||||
"section_mapping_completeness",
|
||||
"required_visual_elements",
|
||||
"prompt_length",
|
||||
"no_forbidden_elements",
|
||||
},
|
||||
RequiredElements: map[string]bool{
|
||||
"subject": true,
|
||||
"scene": true,
|
||||
"lighting": true,
|
||||
},
|
||||
ForbiddenElements: []string{
|
||||
"text_overlay",
|
||||
"collage_style",
|
||||
},
|
||||
RetryMax: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getWeixinPublicAccountImagePromptRule 按业务域获取缓存的规则。
|
||||
func getWeixinPublicAccountImagePromptRule(workflow *weixinPublicAccountWorkflowState) *imagePromptRule {
|
||||
key := workflow.Form.BusinessDomain
|
||||
if rule, ok := weixinPublicAccountImagePromptRuleCache[key]; ok {
|
||||
return rule
|
||||
}
|
||||
cfg := weixinPublicAccountImagePromptConfig
|
||||
rule := &imagePromptRule{
|
||||
Keyword: workflow.Form.Keyword,
|
||||
BusinessDomain: workflow.Form.BusinessDomain,
|
||||
Template: cfg.PromptTemplate,
|
||||
Distribution: cfg.Distribution,
|
||||
BracketFormat: cfg.BracketFormat,
|
||||
}
|
||||
weixinPublicAccountImagePromptRuleCache[key] = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
// buildWeixinPublicAccountImagePromptsV2 新版配图提示词生成逻辑,基于 JSON 配置。
|
||||
func buildWeixinPublicAccountImagePromptsV2(workflow *weixinPublicAccountWorkflowState) []weixinPublicAccountImagePrompt {
|
||||
rule := getWeixinPublicAccountImagePromptRule(workflow)
|
||||
cfg := &rule.Distribution
|
||||
prompts := []weixinPublicAccountImagePrompt{}
|
||||
|
||||
// 1. 封面图
|
||||
if cfg.CoverImage.Enabled {
|
||||
prompts = append(prompts, weixinPublicAccountImagePrompt{
|
||||
Key: cfg.CoverImage.Key,
|
||||
Section: cfg.CoverImage.SectionLbl,
|
||||
Prompt: buildCoverImagePrompt(workflow, rule),
|
||||
Size: cfg.CoverImage.Size,
|
||||
})
|
||||
}
|
||||
|
||||
// 2. 正文配图(按 500 字分布)
|
||||
sections := extractWeixinPublicAccountSections(workflow.Shared.Content)
|
||||
contentImageIndex := 1
|
||||
wordCountAccumulator := 0
|
||||
|
||||
// 根据计算模式决定如何遍历
|
||||
if cfg.ContentImages.Calculation == "accumulate_across_sections" {
|
||||
// 从正文开头累计字数,每 500 字生成一张配图,放在小节开始处
|
||||
for _, section := range sections {
|
||||
sectionWordCount := countChineseWords(section.Content)
|
||||
|
||||
// 如果这是一个新的配图分配点
|
||||
if sectionWordCount > 0 {
|
||||
wordCountAccumulator += sectionWordCount
|
||||
// 计算这之前应该有多少张配图
|
||||
expectedImages := wordCountAccumulator / cfg.WordCount
|
||||
// 添加配图直到达到 expectedImages
|
||||
for wordCountAccumulator >= expectedImages*cfg.WordCount && contentImageIndex <= expectedImages {
|
||||
prompt := buildContentImagePrompt(workflow, section, contentImageIndex, rule)
|
||||
prompts = append(prompts, weixinPublicAccountImagePrompt{
|
||||
Key: fmt.Sprintf("%s_%02d", cfg.ContentImages.KeyPrefix, contentImageIndex),
|
||||
Section: section.Title,
|
||||
Prompt: prompt,
|
||||
Size: cfg.ContentImages.Size,
|
||||
})
|
||||
contentImageIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prompts
|
||||
}
|
||||
|
||||
// buildCoverImagePrompt 生成封面图提示词。
|
||||
func buildCoverImagePrompt(workflow *weixinPublicAccountWorkflowState, rule *imagePromptRule) string {
|
||||
title := firstNonEmpty(workflow.Shared.SelectedTitle, workflow.Form.Keyword, "公众号文章")
|
||||
|
||||
// 从 JSON 模板构建(当前回退到硬编码模板)
|
||||
template := rule.Template.Template
|
||||
if template == "" {
|
||||
// 回退到当前硬编码的英文模板
|
||||
return fmt.Sprintf("realistic editorial illustration, Chinese business article cover, %s, focus on %s, %s, clean composition, natural lighting, brand-safe, no text, no watermark",
|
||||
weixinPublicAccountBusinessDomainLabel(workflow.Form.BusinessDomain),
|
||||
title,
|
||||
"公众号封面主视觉",
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(template, 1, title, fmt.Sprintf("%s(截断至120字)", title))
|
||||
}
|
||||
|
||||
// buildContentImagePrompt 生成正文配图提示词。
|
||||
func buildContentImagePrompt(workflow *weixinPublicAccountWorkflowState, section weixinPublicAccountSection, index int, rule *imagePromptRule) string {
|
||||
summary := truncateChinese(strings.Join(section.Content, " "), 120)
|
||||
|
||||
// 从 JSON 模板构建
|
||||
template := rule.Template.Template
|
||||
if template == "" {
|
||||
// 回退到当前硬编码的英文模板
|
||||
return fmt.Sprintf("realistic editorial illustration, Chinese business article cover, %s, focus on %s, %s, clean composition, natural lighting, brand-safe, no text, no watermark",
|
||||
weixinPublicAccountBusinessDomainLabel(workflow.Form.BusinessDomain),
|
||||
firstNonEmpty(section.Title, workflow.Form.Keyword),
|
||||
firstNonEmpty(summary, workflow.Form.Tone),
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(template, index, firstNonEmpty(section.Title, workflow.Form.Keyword), summary)
|
||||
}
|
||||
|
||||
// countChineseWords 计算内容的中文字数。
|
||||
func countChineseWords(content []string) int {
|
||||
text := strings.Join(content, "\n")
|
||||
count := 0
|
||||
for _, r := range text {
|
||||
if r >= '\u4e00' && r <= '\u9fff' {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// truncateChinese 截断中文字符串到指定长度。
|
||||
func truncateChinese(s string, maxLen int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) > maxLen {
|
||||
return string(runes[:maxLen])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// renderWeixinPublicAccountImagePromptFromJSON 基于 JSON 配置渲染带标记的正文。
|
||||
func renderWeixinPublicAccountImagePromptFromJSON(content string, prompts []weixinPublicAccountImagePrompt) string {
|
||||
cfg := weixinPublicAccountImagePromptConfig
|
||||
content = strings.TrimSpace(content)
|
||||
if content == "" {
|
||||
return ""
|
||||
}
|
||||
blocks := []string{content, "", "## 配图提示词"}
|
||||
|
||||
// 使用 JSON 配置中的括号格式
|
||||
sections := extractWeixinPublicAccountSections(content)
|
||||
sectionIdx := 0
|
||||
|
||||
for _, prompt := range prompts {
|
||||
if prompt.Key == cfg.Distribution.CoverImage.Key {
|
||||
// 封面图:插入在标题下方
|
||||
block := replacePlaceholder(cfg.BracketFormat.Cover, "{prompt}", prompt.Prompt)
|
||||
block = replacePlaceholder(block, "{size}", cfg.Distribution.CoverImage.Size)
|
||||
blocks = append(blocks, block)
|
||||
} else if strings.HasPrefix(prompt.Key, cfg.Distribution.ContentImages.KeyPrefix) {
|
||||
// 正文配图:插入在小节开始处
|
||||
if sectionIdx < len(sections) {
|
||||
block := replacePlaceholder(cfg.BracketFormat.Content, "{prompt}", prompt.Prompt)
|
||||
block = replacePlaceholder(block, "{size}", cfg.Distribution.ContentImages.Size)
|
||||
block = replacePlaceholder(block, "{index}", fmt.Sprintf("%02d", sectionIdx+1))
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
sectionIdx++
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(blocks, "\n")
|
||||
}
|
||||
|
||||
func replacePlaceholder(s, placeholder, value string) string {
|
||||
return strings.ReplaceAll(s, placeholder, value)
|
||||
}
|
||||
+1
@@ -153,6 +153,7 @@ type weixinPublicAccountImagePrompt struct {
|
||||
Key string `json:"key"`
|
||||
Section string `json:"section"`
|
||||
Prompt string `json:"prompt"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
type weixinPublicAccountImageRef struct {
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# 公众号创作专员 - 规范定义
|
||||
|
||||
> 这是 pj0235 的第一个"配置驱动"专员样板。参考 pj0012 的 JSON 配置体系设计。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
spec/
|
||||
├── config/
|
||||
│ ├── skill.md # 技能描述 + 执行策略(人可读 + LLM 可读)
|
||||
│ ├── workflow.json # 工作流步骤定义(步骤 key、标签、前置条件、表单)
|
||||
│ ├── image_prompt.json # 配图提示词规则(分布、模板、验证)
|
||||
│ ├── domains.json # 业务域配置
|
||||
│ └── policy.json # 重试和降级策略
|
||||
├── config/prompts/
|
||||
│ └── image_prompt_generate.prompt.md # 配图提示词生成 prompt 模板
|
||||
├── config/validators/
|
||||
│ └── image_prompt.json # 配图提示词验证规则
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. **三层分离**:
|
||||
- `skill.md` — 技能描述层(人可读 + LLM 可读)
|
||||
- `*.json` — 数据配置层(机器可读,热更新)
|
||||
- `*.prompt.md` — 提示词模板层(YAML 变量注入)
|
||||
|
||||
2. **代码是运行时适配器**:
|
||||
- 按路径读取 JSON/文本
|
||||
- 将运行时参数注入模板
|
||||
- 执行验证器逻辑
|
||||
- 控制重试/降级循环
|
||||
- 调用 LLM
|
||||
|
||||
3. **不硬编码业务内容**:
|
||||
- 业务规则变更通过修改 JSON/Markdown 实现
|
||||
- 代码层只做通用适配
|
||||
|
||||
## 配图规则(核心示例)
|
||||
|
||||
配图提示词规则完全外置在 `image_prompt.json` 中,包括:
|
||||
|
||||
- 分布规则:封面图 + 每 500 字 1 张
|
||||
- 提示词模板:中文模板 + 最小/最大字数
|
||||
- 括号标记格式:`[TITLE_PIC: 提示词, size: landscape_16_9]`
|
||||
- 验证规则:主体/场景/光线必填,文字覆盖/拼贴禁用
|
||||
- 重试策略:最多 2 次
|
||||
|
||||
## 如何参考
|
||||
|
||||
完整的学习分析见:`../../../../reference/pj0012-wechat-image-spec.md`
|
||||
|
||||
## 使用方式
|
||||
|
||||
Go 代码通过 `embed.FS` 嵌入这些文件,运行时:
|
||||
1. 读取 JSON 配置 → 获取业务规则
|
||||
2. 读取 prompt 模板 → 注入运行时参数 → 组装最终 prompt
|
||||
3. 读取验证器 → 执行验证逻辑
|
||||
4. 读取策略 → 控制重试/降级循环
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"description": "业务域配置。每个业务域有对应的标签和默认配置。",
|
||||
"version": "1.0.0",
|
||||
"domains": {
|
||||
"AI": {
|
||||
"label": "AI",
|
||||
"image_domain_label": "AI 与人工智能",
|
||||
"hotspot_sources": ["hackernews", "techcrunch", "arxiv"],
|
||||
"default_keywords": ["大语言模型", "AI Agent", "多模态", "AIGC"]
|
||||
},
|
||||
"medical": {
|
||||
"label": "医疗",
|
||||
"image_domain_label": "医疗健康",
|
||||
"hotspot_sources": ["medical_news_today", "statnews"],
|
||||
"default_keywords": ["新药研发", "临床实验", "健康管理", "医疗AI"]
|
||||
},
|
||||
"insurance": {
|
||||
"label": "保险",
|
||||
"image_domain_label": "保险金融",
|
||||
"hotspot_sources": ["reuters", "bloomberg"],
|
||||
"default_keywords": ["健康险", "车险", "理赔", "保险科技"]
|
||||
},
|
||||
"hr": {
|
||||
"label": "HR",
|
||||
"image_domain_label": "人力资源",
|
||||
"hotspot_sources": ["linkedin", "shrm"],
|
||||
"default_keywords": ["招聘", "培训", "绩效考核", "薪酬福利"]
|
||||
},
|
||||
"legal": {
|
||||
"label": "法务",
|
||||
"image_domain_label": "法律合规",
|
||||
"hotspot_sources": ["reuters", "bloomberg"],
|
||||
"default_keywords": ["数据合规", "知识产权", "劳动法", "合同法"]
|
||||
},
|
||||
"general": {
|
||||
"label": "通用",
|
||||
"image_domain_label": "通用主题",
|
||||
"hotspot_sources": ["google_trending", "reddit"],
|
||||
"default_keywords": []
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"description": "配图提示词生成规则。配图按字数均匀分布,封面图在标题下方,其余配图按 500 字间隔放置在所属小节开始处。",
|
||||
"version": "1.0.0",
|
||||
"distribution": {
|
||||
"mode": "word_count",
|
||||
"word_count_threshold": 500,
|
||||
"cover_image": {
|
||||
"enabled": true,
|
||||
"key": "cover",
|
||||
"slot_label": "TITLE_PIC",
|
||||
"section_label": "封面配图",
|
||||
"position": "after_title",
|
||||
"size": "landscape_16_9"
|
||||
},
|
||||
"content_images": {
|
||||
"enabled": true,
|
||||
"key_prefix": "section",
|
||||
"size": "landscape_4_3",
|
||||
"placement": "section_start",
|
||||
"calculation": "accumulate_across_sections"
|
||||
}
|
||||
},
|
||||
"prompt_template": {
|
||||
"description": "配图提示词模板。{index} 为序号(从 1 开始),{section_title} 为小节标题,{section_summary} 为段落摘要(截断至 120 字)。",
|
||||
"format": "chinese",
|
||||
"min_chars": 40,
|
||||
"max_chars": 70,
|
||||
"template": "真实人物纪实摄影,公众号文章配图,第{index}张配图核心画面,{section_summary},构图清晰,主体突出,电影感光影,细节丰富,高清"
|
||||
},
|
||||
"bracket_format": {
|
||||
"description": "配图提示词在正文中的嵌入格式。",
|
||||
"cover": "[TITLE_PIC: {prompt}, size: {size}]",
|
||||
"content": "[section_{{index:02d}}: {prompt}, size: {size}]"
|
||||
},
|
||||
"validation": {
|
||||
"checks": [
|
||||
"section_mapping_completeness",
|
||||
"required_visual_elements",
|
||||
"prompt_length",
|
||||
"no_forbidden_elements"
|
||||
],
|
||||
"required_elements": {
|
||||
"subject": true,
|
||||
"scene": true,
|
||||
"lighting": true
|
||||
},
|
||||
"forbidden_elements": [
|
||||
"text_overlay",
|
||||
"collage_style"
|
||||
],
|
||||
"retry_max": 2,
|
||||
"retry_feedback_template": "配图提示词校验失败:{failed_checks}。请重新生成全部提示词并修复问题,保持段落映射不变。"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"description": "重试和降级策略。",
|
||||
"version": "1.0.0",
|
||||
"retries": {
|
||||
"default_max_retries": 2,
|
||||
"step_overrides": {
|
||||
"topic_recommendation": 1,
|
||||
"title_generation": 2,
|
||||
"outline_generation": 2,
|
||||
"content_creation": 2,
|
||||
"image_prompt_generation": 2,
|
||||
"image_generation": 2,
|
||||
"preview_export": 0,
|
||||
"page_markdown": 0
|
||||
},
|
||||
"policy": {
|
||||
"regenerate_fully": true,
|
||||
"include_error_message_in_feedback": true,
|
||||
"allow_partial_patch": false
|
||||
}
|
||||
},
|
||||
"fallback": {
|
||||
"mark_fallback_in_logs": true,
|
||||
"step_overrides": {
|
||||
"topic_recommendation": {
|
||||
"allow_fallback": true,
|
||||
"fallback_note": "使用关键词直接生成选题。"
|
||||
},
|
||||
"title_generation": {
|
||||
"allow_fallback": true,
|
||||
"fallback_note": "使用标题模板生成。"
|
||||
},
|
||||
"content_creation": {
|
||||
"allow_fallback": false,
|
||||
"fallback_note": "不降级,返回明确错误。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Role
|
||||
你是 `image_prompt_generate` 步骤的执行者,负责为公众号文章生成配图提示词。
|
||||
|
||||
# Goal
|
||||
基于正文内容,按字数分布规则生成配图提示词,并将提示词以括号标记格式嵌入正文。
|
||||
|
||||
# 配图规则
|
||||
- 封面图:标题下方 1 张,`[TITLE_PIC: 提示词, size: landscape_16_9]`
|
||||
- 正文配图:从正文开头累计字数,每 500 字生成 1 张
|
||||
- 每张配图放在所属小节(##)的开始处
|
||||
- 如果某节超过 500 字,则该节内按 500 字间隔插入多张
|
||||
- 提示词格式:`[section_{{index:02d}}: 提示词, size: landscape_4_3]`
|
||||
|
||||
# 提示词模板
|
||||
```
|
||||
真实人物纪实摄影,公众号文章配图,第{N}张配图核心画面,{段落摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清
|
||||
```
|
||||
|
||||
# 约束
|
||||
- 每个提示词必须包含:主体、场景、光线
|
||||
- 每个提示词长度 40-70 个中文字
|
||||
- 禁止文字覆盖、拼贴风格
|
||||
- 段落映射必须完整(配图数 = 需要配图的小节数)
|
||||
|
||||
# 输出
|
||||
- `image_prompts`: 配图提示词数组 [{key, section, prompt, size}]
|
||||
- `content_with_prompts`: 带括号标记的完整正文
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# 公众号创作专员技能规范
|
||||
|
||||
## 技能标识
|
||||
- **Key**: weixin-public-account
|
||||
- **Label**: 公众号创作专员
|
||||
- **Type**: longform-writing
|
||||
|
||||
## 目的
|
||||
完成公众号文章的端到端创作:从选题、标题、提纲、正文、配图提示词、图片生成、预览导出到分页 Markdown。
|
||||
|
||||
## 适用场景
|
||||
- 热点选题驱动的内容创作
|
||||
- 关键词驱动的通用文章生成
|
||||
- 多业务域(AI/医疗/保险/HR/法务/通用)适配
|
||||
|
||||
## 工作流步骤
|
||||
|
||||
| 序号 | Step Key | 标签 | 前置条件 | 输出 |
|
||||
|------|----------|------|----------|------|
|
||||
| 0 | task_configuration | 任务配置 | — | 业务域、关键词、受众、语调等表单参数 |
|
||||
| 1 | topic_recommendation | 选题推荐 | 配置完成 | 3 个选题建议(标题 + 理由 + 热度) |
|
||||
| 2 | title_generation | 标题生成 | 有选题 | 3 个标题建议(偏点击/偏稳重/偏平衡) |
|
||||
| 3 | outline_generation | 提纲生成 | 有标题 | Markdown 提纲结构 |
|
||||
| 4 | content_creation | 正文创作 | 有提纲 | Markdown 正文(含 ## 小节标题) |
|
||||
| 5 | image_prompt_generation | 配图提示词 | 有正文 | 配图提示词 + 带标记正文 |
|
||||
| 6 | image_generation | 图片生成 | 有配图提示词 | 封面图 + 段落图 |
|
||||
| 7 | preview_export | 预览导出 | 有正文 + 图片 | 预览 MD + 预览 HTML |
|
||||
| 8 | page_markdown | 分页 Markdown | 有预览稿 | 分页 MD(含图片尺寸标注) |
|
||||
|
||||
## 配图规则
|
||||
|
||||
### 分布规则
|
||||
1. 标题下方生成 1 张封面图(`cover` / `TITLE_PIC`),16:9
|
||||
2. 从正文开头累计字数,每 500 字生成 1 张配图
|
||||
3. 每张配图放在其所属小节(##)的**开始处**
|
||||
4. 如果某节很长(>500 字),则该节内按 500 字间隔插入多张
|
||||
5. 如果某节不足 500 字,与下节合并累计,但不跨节重复
|
||||
|
||||
### 提示词模板
|
||||
```
|
||||
真实人物纪实摄影,公众号文章配图,第{N}张配图核心画面,{段落摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清
|
||||
```
|
||||
|
||||
### 括号标记格式
|
||||
```
|
||||
[TITLE_PIC: 提示词, size: landscape_16_9]
|
||||
[section_01: 提示词, size: landscape_4_3]
|
||||
```
|
||||
|
||||
## 业务域
|
||||
AI、医疗、保险、HR、法务、通用
|
||||
|
||||
## 重试策略
|
||||
- 热点抓取:最多 1 次重试
|
||||
- 选题推荐:最多 1 次重试
|
||||
- 标题/提纲/正文/配图:最多 2 次重试
|
||||
- 预览/分页:0 次重试
|
||||
|
||||
## 验证规则
|
||||
- 提纲:至少 3 个小节,每小节至少 2 个要点
|
||||
- 正文:至少 800 字,每个小节有实质性内容
|
||||
- 配图提示词:段落数 = 提示词数,每个提示词 40-70 字,必须包含主体/场景/光线
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"description": "配图提示词验证规则。",
|
||||
"version": "1.0.0",
|
||||
"step_id": "image_prompt_generation",
|
||||
"checks": [
|
||||
"section_mapping_completeness",
|
||||
"required_visual_elements",
|
||||
"prompt_length",
|
||||
"no_forbidden_elements",
|
||||
"bracket_format_valid"
|
||||
],
|
||||
"section_mapping": {
|
||||
"description": "配图提示词与正文段落的映射必须完整。",
|
||||
"cover_required": true,
|
||||
"content_images_required": true
|
||||
},
|
||||
"visual_elements": {
|
||||
"required": ["subject", "scene", "lighting"],
|
||||
"forbidden": ["text_overlay", "collage_style"]
|
||||
},
|
||||
"prompt_length": {
|
||||
"min_chars": 40,
|
||||
"max_chars": 70,
|
||||
"count_mode": "chinese_characters"
|
||||
},
|
||||
"bracket_format": {
|
||||
"cover_pattern": "^\\[TITLE_PIC: .* size: landscape_16_9\\]$",
|
||||
"content_pattern": "^\\[section_\\d{2}: .* size: landscape_4_3\\]$"
|
||||
},
|
||||
"retry_feedback_template": "配图提示词校验失败:{failed_checks}。请重新生成全部提示词并修复问题。",
|
||||
"retry_max": 2
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"description": "公众号创作工作流步骤定义。步骤按顺序执行,前一步骤完成方可进入下一步骤。",
|
||||
"version": "1.0.0",
|
||||
"steps": [
|
||||
{
|
||||
"key": "task_configuration",
|
||||
"label": "任务配置",
|
||||
"type": "form",
|
||||
"order": 0,
|
||||
"is_entry_point": true,
|
||||
"description": "配置业务域、关键词、受众、语调等参数。"
|
||||
},
|
||||
{
|
||||
"key": "topic_recommendation",
|
||||
"label": "选题推荐",
|
||||
"type": "ai",
|
||||
"order": 1,
|
||||
"preconditions": ["task_configuration"],
|
||||
"description": "基于业务域和关键词生成 3 个选题建议。"
|
||||
},
|
||||
{
|
||||
"key": "title_generation",
|
||||
"label": "标题生成",
|
||||
"type": "ai",
|
||||
"order": 2,
|
||||
"preconditions": ["topic_recommendation"],
|
||||
"description": "基于选定选题生成 3 个标题建议(偏点击/偏稳重/偏平衡)。"
|
||||
},
|
||||
{
|
||||
"key": "outline_generation",
|
||||
"label": "提纲生成",
|
||||
"type": "ai",
|
||||
"order": 3,
|
||||
"preconditions": ["title_generation"],
|
||||
"description": "基于选定标题生成 Markdown 提纲结构。"
|
||||
},
|
||||
{
|
||||
"key": "content_creation",
|
||||
"label": "正文创作",
|
||||
"type": "ai",
|
||||
"order": 4,
|
||||
"preconditions": ["outline_generation"],
|
||||
"description": "基于选定提纲和配置参数生成正文。"
|
||||
},
|
||||
{
|
||||
"key": "image_prompt_generation",
|
||||
"label": "配图提示词",
|
||||
"type": "ai",
|
||||
"order": 5,
|
||||
"preconditions": ["content_creation"],
|
||||
"description": "基于正文按字数分布生成配图提示词,并嵌入带标记正文。"
|
||||
},
|
||||
{
|
||||
"key": "image_generation",
|
||||
"label": "图片生成",
|
||||
"type": "external",
|
||||
"order": 6,
|
||||
"preconditions": ["image_prompt_generation"],
|
||||
"description": "基于配图提示词调用外部图片生成 API。"
|
||||
},
|
||||
{
|
||||
"key": "preview_export",
|
||||
"label": "预览导出",
|
||||
"type": "render",
|
||||
"order": 7,
|
||||
"preconditions": ["content_creation", "image_generation"],
|
||||
"description": "生成预览 MD、预览 HTML 和分页 MD。"
|
||||
},
|
||||
{
|
||||
"key": "page_markdown",
|
||||
"label": "分页 Markdown",
|
||||
"type": "render",
|
||||
"order": 8,
|
||||
"preconditions": ["preview_export"],
|
||||
"description": "生成带图片尺寸标注的分页 Markdown。"
|
||||
}
|
||||
],
|
||||
"forms": {
|
||||
"task_configuration": {
|
||||
"fields": [
|
||||
{
|
||||
"key": "business_domain",
|
||||
"label": "业务域",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": ["AI", "医疗", "保险", "HR", "法务", "通用"]
|
||||
},
|
||||
{
|
||||
"key": "keyword",
|
||||
"label": "关键词",
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "audience",
|
||||
"label": "目标受众",
|
||||
"type": "text",
|
||||
"default": "公众号读者",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "goal",
|
||||
"label": "写作目标",
|
||||
"type": "text",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "tone",
|
||||
"label": "语调风格",
|
||||
"type": "select",
|
||||
"default": "专业但好懂",
|
||||
"options": ["专业但好懂", "轻松有趣", "严肃权威", "故事叙述", "数据驱动"]
|
||||
},
|
||||
{
|
||||
"key": "requirements",
|
||||
"label": "特殊要求",
|
||||
"type": "textarea",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "outline_style",
|
||||
"label": "提纲风格",
|
||||
"type": "select",
|
||||
"default": "问题拆解型",
|
||||
"options": ["问题拆解型", "对比分析型", "案例驱动型", "步骤教程型"]
|
||||
},
|
||||
{
|
||||
"key": "outline_words",
|
||||
"label": "提纲字数",
|
||||
"type": "number",
|
||||
"default": 800,
|
||||
"min": 300,
|
||||
"max": 2000,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "content_target_words",
|
||||
"label": "正文目标字数",
|
||||
"type": "number",
|
||||
"default": 1400,
|
||||
"min": 500,
|
||||
"max": 5000,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "max_content_images",
|
||||
"label": "最大配图数(可选,0 表示无上限)",
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 20,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
-2
@@ -158,9 +158,9 @@
|
||||
class="oa-btn oa-btn-tiny"
|
||||
type="button"
|
||||
:disabled="previewLoading"
|
||||
@click="openArtifactPreview(file)"
|
||||
@click="downloadFile(file)"
|
||||
>
|
||||
预览
|
||||
下载
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,6 +169,50 @@
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 整体导出区:所有格式一次导出 -->
|
||||
<section class="oa-stack-section">
|
||||
<div class="oa-stack-head">
|
||||
<span class="oa-stack-title">整体导出</span>
|
||||
</div>
|
||||
<div v-if="hasTask" class="oa-export-row">
|
||||
<button
|
||||
class="oa-btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="download('preview_md')"
|
||||
>
|
||||
预览 MD
|
||||
</button>
|
||||
<button
|
||||
class="oa-btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="download('preview_html')"
|
||||
>
|
||||
预览 HTML
|
||||
</button>
|
||||
<button
|
||||
class="oa-btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="download('pagemd')"
|
||||
>
|
||||
分页 MD
|
||||
</button>
|
||||
<button
|
||||
class="oa-btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="download('docx')"
|
||||
>
|
||||
Word (.docx)
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="oa-empty oa-empty-inline">
|
||||
没有可导出的内容,先运行创作流程。
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
@@ -1085,6 +1129,40 @@ async function download(format) {
|
||||
}
|
||||
}
|
||||
|
||||
// 从产物文件下载(HTML / MD 直接下载,图片通过 API 获取)
|
||||
async function downloadFile(file) {
|
||||
loading.value = true
|
||||
try {
|
||||
let blob = null
|
||||
let filename = file.name || 'download'
|
||||
|
||||
if (file.kind === 'html' && file.html) {
|
||||
blob = new Blob([file.html], { type: 'text/html' })
|
||||
} else if (file.kind === 'markdown' && file.text) {
|
||||
blob = new Blob([file.text], { type: 'text/markdown' })
|
||||
} else if (file.kind === 'image' && file.url) {
|
||||
blob = await fetchWeixinPublicAccountGeneratedImage(file.url)
|
||||
if (file.name) filename = file.name
|
||||
}
|
||||
|
||||
if (blob) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success('下载成功')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || '下载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function exportFileName(format) {
|
||||
if (format === 'preview_html') return 'weixin-public-account-preview.html'
|
||||
if (format === 'pagemd') return 'weixin-public-account-pagemd.md'
|
||||
|
||||
@@ -474,7 +474,13 @@ function renderMarkdownBlock(block) {
|
||||
const headingMatch = trimmed.match(/^(#{1,3})\s+(.+)$/)
|
||||
if (headingMatch) {
|
||||
const level = headingMatch[1].length
|
||||
return `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`
|
||||
const heading = `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`
|
||||
// If the block has more lines after the heading, render them as additional content
|
||||
const afterHeading = trimmed.split('\n', 2)[1]
|
||||
if (afterHeading && afterHeading.trim()) {
|
||||
return heading + ' ' + renderMarkdownBlock(afterHeading.trim())
|
||||
}
|
||||
return heading
|
||||
}
|
||||
|
||||
const lines = trimmed.split('\n')
|
||||
@@ -792,7 +798,7 @@ function getWeixinPublicAccountStep(payload, stepKey) {
|
||||
return payload?.workflow?.steps?.[stepKey] || {}
|
||||
}
|
||||
|
||||
function trimWeixinPublicAccountText(value, maxLength = 180) {
|
||||
function trimWeixinPublicAccountText(value, maxLength = 360) {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) return ''
|
||||
return text.length > maxLength ? `${text.slice(0, maxLength).trim()}...` : text
|
||||
@@ -876,7 +882,7 @@ function buildWeixinPublicAccountOutlineStageMessage(payload) {
|
||||
'### 3. 提纲步骤已完成',
|
||||
sections.length
|
||||
? `本轮提纲主结构:\n${formatWeixinPublicAccountBulletList(sections)}`
|
||||
: trimWeixinPublicAccountText(outline, 220) || '本轮提纲已生成。',
|
||||
: trimWeixinPublicAccountText(outline, 500) || '本轮提纲已生成。',
|
||||
].filter(Boolean).join('\n\n'),
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
}
|
||||
@@ -892,7 +898,7 @@ function buildWeixinPublicAccountContentStageMessage(payload) {
|
||||
.replace(/^>\s+.*$/gm, '')
|
||||
.replace(/\n{2,}/g, '\n')
|
||||
.trim(),
|
||||
220,
|
||||
500,
|
||||
)
|
||||
return {
|
||||
role: 'assistant',
|
||||
@@ -910,15 +916,18 @@ function buildWeixinPublicAccountImagePromptStageMessage(payload) {
|
||||
const output = step.output || {}
|
||||
const count = Number(output.count || 0)
|
||||
const prompts = Array.isArray(output.image_prompts) ? output.image_prompts.slice(0, 4) : []
|
||||
const lines = [
|
||||
`已提炼 ${count} 条配图提示词。`,
|
||||
...(prompts.length
|
||||
? prompts.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 80)}`)
|
||||
: []),
|
||||
]
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: [
|
||||
'### 5. 配图提示词步骤已完成',
|
||||
count > 0 ? `已提炼 ${count} 条配图提示词。` : '本轮未提炼出可用配图提示词。',
|
||||
...(prompts.length
|
||||
? prompts.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 80)}`)
|
||||
: []),
|
||||
].filter(Boolean).join('\n'),
|
||||
lines.length ? lines.join('\n') : '本轮未提炼出可用配图提示词。',
|
||||
].filter(Boolean).join('\n\n'),
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
}
|
||||
}
|
||||
@@ -929,15 +938,18 @@ function buildWeixinPublicAccountImageGenerationStageMessage(payload) {
|
||||
const output = step.output || {}
|
||||
const count = Number(output.count || 0)
|
||||
const refs = Array.isArray(output.image_refs) ? output.image_refs.slice(0, 4) : []
|
||||
const lines = [
|
||||
`已生成 ${count} 张配图。`,
|
||||
...(refs.length
|
||||
? refs.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 60)}`)
|
||||
: []),
|
||||
]
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: [
|
||||
'### 6. 图片生成步骤已完成',
|
||||
count > 0 ? `已生成 ${count} 张配图。` : '本轮未生成图片结果。',
|
||||
...(refs.length
|
||||
? refs.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 60)}`)
|
||||
: []),
|
||||
].filter(Boolean).join('\n'),
|
||||
lines.length ? lines.join('\n') : '本轮未生成图片结果。',
|
||||
].filter(Boolean).join('\n\n'),
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
}
|
||||
}
|
||||
@@ -952,7 +964,7 @@ function buildWeixinPublicAccountPreviewStageMessage(payload) {
|
||||
content: [
|
||||
'### 7. 预览与导出步骤已完成',
|
||||
`已整合正文${imageCount ? `与 ${imageCount} 张配图` : ''}生成预览稿,可检查排版并导出。`,
|
||||
].filter(Boolean).join('\n'),
|
||||
].filter(Boolean).join('\n\n'),
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
}
|
||||
}
|
||||
@@ -967,7 +979,7 @@ function buildWeixinPublicAccountPageMarkdownStageMessage(payload) {
|
||||
content: [
|
||||
'### 8. 分页 MD 步骤已完成',
|
||||
count > 0 ? `已按段落结构生成 ${count} 页分页 MD。` : '分页 MD 已生成。',
|
||||
].filter(Boolean).join('\n'),
|
||||
].filter(Boolean).join('\n\n'),
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# pj0012 配图提示词规则参考
|
||||
|
||||
> 来源:/home/eaiadmin/eaifiles/codebase/pj0012-aiw-mbai-u/
|
||||
> 用于指导 pj0235 的配图提示词规则设计。
|
||||
|
||||
## 一、整体架构
|
||||
|
||||
pj0012 将配图规则完全外置为 **JSON 配置文件 + Markdown 提示词模板**,代码层不做硬编码。
|
||||
|
||||
```
|
||||
config/wechat/
|
||||
├── skills/wechat_article_skill_ITandAI/
|
||||
│ ├── prompts/image_prompt_generate.prompt.md # 提示词模板
|
||||
│ ├── validators/image.validator.json # 验证规则
|
||||
│ └── retries/retry-policy.json # 重试策略
|
||||
├── wechat_default_image_requirements.json # 默认配图要求
|
||||
├── wechat_working_image_requirements.json # 运行时配图要求
|
||||
└── accounts/<account_id>/wechat_working_image_requirements.json # 账号级覆盖
|
||||
```
|
||||
|
||||
## 二、配图提示词生成规则
|
||||
|
||||
### 2.1 分布规则
|
||||
|
||||
- **标题下方主图**(`TITLE_PIC`):第 1 段对应,1 张,16:9 横图
|
||||
- **段落配图**(`IMAGE_PROMPT_01`, `IMAGE_PROMPT_02`...):按正文段落 1:1 生成
|
||||
- **正文分割方式**:按 `#` 标题或双空行分割为段落
|
||||
|
||||
### 2.2 括号标记格式
|
||||
|
||||
配图提示词以括号标记形式嵌入正文,格式为:
|
||||
|
||||
```
|
||||
[槽位名: 提示词内容, size: 宽x高]
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```markdown
|
||||
[ TITLE_PIC: 封面配图提示词, size: 1024x576 ]
|
||||
[ IMAGE_PROMPT_01: 第1段配图提示词, size: 1024x576 ]
|
||||
[ IMAGE_PROMPT_02: 第2段配图提示词, size: 1024x576 ]
|
||||
```
|
||||
|
||||
每个标记插入在对应段落下方,后续图片生成步骤从中解析出 key、prompt、size。
|
||||
|
||||
### 2.3 提示词模板
|
||||
|
||||
```
|
||||
真实人物纪实摄影,公众号文章配图,第{N}段核心画面,{段落内容摘要120字},构图清晰,主体突出,电影感光影,细节丰富,高清
|
||||
```
|
||||
|
||||
**模板要素**:
|
||||
- 风格前缀:`真实人物纪实摄影,公众号文章配图`
|
||||
- 序号:`第{N}段核心画面`
|
||||
- 内容摘要:段落内容截断至 120 字
|
||||
- 视觉要求:`构图清晰,主体突出,电影感光影,细节丰富,高清`
|
||||
- 长度约束:**40-70 个中文字**
|
||||
|
||||
### 2.4 全局规则(image.validator.json)
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 必须包含主体 | 每个提示词必须描述画面主体 |
|
||||
| 必须包含场景 | 每个提示词必须描述画面场景 |
|
||||
| 必须包含光线 | 每个提示词必须描述光线效果 |
|
||||
| 禁止文字覆盖 | 不允许提示词中包含文字说明 |
|
||||
| 禁止拼贴风格 | 不允许拼贴/ collage 风格 |
|
||||
| 段落映射完整 | 段落数必须与提示词数一致 |
|
||||
|
||||
### 2.5 重试策略
|
||||
|
||||
- 默认最大重试次数:2 次
|
||||
- 每次重试将失败的检查项反馈给 LLM
|
||||
- 重试耗尽后降级为模板拼接(兜底)
|
||||
|
||||
### 2.6 图片规格
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|------|
|
||||
| 尺寸比例 | 16:9 横版 |
|
||||
| 分辨率 | 1792x1024 |
|
||||
| 提示词标签 | 1024x576 |
|
||||
|
||||
### 2.7 账号级风格覆盖
|
||||
|
||||
每个账号可独立配置图片风格,存储在 `wechat_working_image_requirements.json`:
|
||||
|
||||
| 账号 | 风格 | 数量策略 |
|
||||
|------|------|---------|
|
||||
| default | 真实人物纪实摄影 | 按正文段落数量生成 |
|
||||
| account | 美食摄影风格 | 根据菜品种类灵活配图 |
|
||||
| account-01a312 | 通信科技行业视觉 | 关键概念一图一景 |
|
||||
| account-73971d | 未来科技自然动感 | 按技术模块分层展示 |
|
||||
|
||||
## 三、专员 JSON 定义体系
|
||||
|
||||
### 3.1 技能包(Skill Package)结构
|
||||
|
||||
```
|
||||
config/{workspace}/skills/{skill_name}/
|
||||
├── SKILL.md # 技能描述 + 执行策略
|
||||
├── prompts/
|
||||
│ ├── {step_id}.prompt.md # 步骤提示词模板(YAML 变量注入)
|
||||
│ └── ...
|
||||
├── validators/
|
||||
│ ├── {step_id}.validator.json # 验证规则
|
||||
│ └── ...
|
||||
├── styles/
|
||||
│ ├── style-01-*.json # 文体变体
|
||||
│ └── ...
|
||||
├── retries/
|
||||
│ └── retry-policy.json # 重试策略
|
||||
└── fallbacks/
|
||||
├── fallback-policy.json # 降级策略
|
||||
└── {step_id}.fallback.json # 步骤级降级资产
|
||||
```
|
||||
|
||||
### 3.2 配置文件分类
|
||||
|
||||
| 类型 | 文件 | 内容 | 读取时机 |
|
||||
|------|------|------|---------|
|
||||
| 技能描述 | SKILL.md | 名称、描述、适用场景、执行流程 | 路由选择 |
|
||||
| 提示词模板 | prompts/*.prompt.md | LLM 提示词({{变量}} 注入) | 步骤执行 |
|
||||
| 验证规则 | validators/*.json | 检查清单、质量规则、重试反馈 | 输出验证 |
|
||||
| 文体变体 | styles/*.json | 风格偏好、关注点、验证器强调 | 步骤执行 |
|
||||
| 重试策略 | retries/*.json | 每步骤最大重试次数、策略 | 循环控制 |
|
||||
| 降级资产 | fallbacks/*.json | 降级提示词、默认值 | 重试耗尽 |
|
||||
| 业务数据 | *_working_*.json | 当前活跃配置(无缓存) | 运行时 |
|
||||
|
||||
### 3.3 提示词模板格式
|
||||
|
||||
```markdown
|
||||
# prompts/outline_generate.prompt.md
|
||||
|
||||
# Role
|
||||
You are the `outline_generate` draft executor for `wechat_article_skill_ITandAI`.
|
||||
|
||||
# Goal
|
||||
Generate the first draft of...
|
||||
|
||||
# Runtime Context
|
||||
{{runtime_inputs}} # 运行时注入
|
||||
|
||||
# Contract Excerpt
|
||||
{{contract_excerpt}}
|
||||
|
||||
# Validator Excerpt
|
||||
{{validator_excerpt}}
|
||||
```
|
||||
|
||||
变量解析:`prompt.replace("{{var_name}}", value)`
|
||||
|
||||
### 3.4 验证器 JSON 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"step_id": "outline_generate",
|
||||
"checks": ["markdown_structure", "required_sections", ...],
|
||||
"word_budget_rules": { ... },
|
||||
"repair_strategy": {
|
||||
"mode": "draft_then_repair",
|
||||
"repair_prompt_asset": "prompts/outline_repair.prompt.md",
|
||||
"reuse_last_draft_on_next_round": true
|
||||
},
|
||||
"quality_rules": {
|
||||
"min_bullet_chars": 18,
|
||||
"ban_generic_patterns": ["^先用.+切入", ...],
|
||||
"placeholder_phrases": ["相关信号", ...]
|
||||
},
|
||||
"retry_feedback_template": "The outline draft failed validation. Fix: {failed_checks}"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 重试策略 JSON 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"default_max_retries": 2,
|
||||
"step_overrides": {
|
||||
"hot_topic_extract": 1,
|
||||
"topic_refine": 1,
|
||||
"outline_generate": 2,
|
||||
"content_generate": 2,
|
||||
"image_prompt_generate": 2,
|
||||
"pagemd_prepare": 0
|
||||
},
|
||||
"policy": {
|
||||
"regenerate_fully": true,
|
||||
"include_failed_checks_in_feedback": true,
|
||||
"allow_partial_patch": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 多账号覆盖机制
|
||||
|
||||
```
|
||||
config/wechat/ # 全局默认
|
||||
├── wechat_working_image_requirements.json
|
||||
├── wechat_working_outline_templates.json
|
||||
└── accounts/
|
||||
├── default/ # 默认账号覆盖
|
||||
├── account/ # 账号 "account" 覆盖
|
||||
└── account-01a312/ # 账号 "account-01a312" 覆盖
|
||||
├── wechat_working_image_requirements.json
|
||||
└── wechat_working_outline_templates.json
|
||||
```
|
||||
|
||||
加载优先级:`accounts/{account_id}/` → 全局默认 → 硬编码兜底
|
||||
|
||||
### 3.7 热更新机制
|
||||
|
||||
- 每次请求重新读取 JSON 文件,**不缓存**
|
||||
- 修改配置文件后下次请求即生效,**无需重启**
|
||||
- 文件不存在时返回空字典(安全降级)
|
||||
- 解析异常时返回空字典(安全降级)
|
||||
|
||||
## 四、核心设计原则
|
||||
|
||||
1. **三层分离**:Contract(契约层,人可读)→ Truth(数据层,机器可读)→ Skill(执行层,代码适配)
|
||||
2. **代码是运行时适配器**:按路径读取 JSON/文本 → 注入运行时参数 → 执行验证 → 控制重试 → 调用 LLM
|
||||
3. **不硬编码业务内容**:业务规则变更全部通过修改 JSON/Markdown 实现
|
||||
4. **优先级链**:运行时配置 > 全局默认 > 账号覆盖 > 硬编码兜底
|
||||
Regular → Executable
+8
@@ -53,6 +53,14 @@ EOF
|
||||
shift
|
||||
done
|
||||
|
||||
# ================================================================
|
||||
# 端口说明
|
||||
# 10231 - 前端 Vite 开发服务器 (dev only)
|
||||
# 10232 - 后端 Go API 服务 (dev only)
|
||||
# 8080 - 外部 LLM 服务 (llamacpp) — AI 对话 API 入口
|
||||
# 8081 - 外部 Embed 服务 (llamacpp) — 向量嵌入服务
|
||||
# 11434 - 外部 Ollama 服务 (如使用 Ollama 路由)
|
||||
# ================================================================
|
||||
BACKEND_PORT=10232
|
||||
FRONTEND_PORT=10231
|
||||
|
||||
|
||||
Reference in New Issue
Block a user