🚀 Dify 第三引擎 (AI Researcher) 全自动化部署指南
在这个工作流中,前半部分(搜索 + 撰写)是全新的,而后半部分(代码清洗 +HTTP 发布)将 100% 复用 我们在引擎一中打磨完美的“防弹架构”。
请在 Dify 中新建一个 工作流 (Workflow),并按以下步骤依次连接节点:
节点 1:Start (开始)
这是整个研究引擎的输入端。你需要告诉 AI 今天研究什么主题。
添加变量:点击
+
变量名:填写 topic
变量类型:选择 String (短文本)
是否必填:是
节点 2:Tool (工具节点 - 全网搜索)
让 Dify 具备联网搜索的能力。
添加节点:选择
Tool (工具)。
选择工具:推荐使用 DuckDuckGo Search 或 Tavily Search(如果你配置了 API 的话,Google Search 也可以)。
输入参数 (Query/Search Input):点击 {x} 插入变量,选择 {{#Start.topic#}}。
_(注:这个节点运行后,会输出一段包含多篇网页搜索结果、摘要和链接的 JSON 或文本。)_
节点 3:LLM (AI 研究员与撰稿人)
这是引擎的大脑,负责把零散的搜索结果融合成一篇有深度、排版精美的万字长文。
模型选择:选择
qwen3-max 或 kimi-k2.5(必须是长文本强逻辑模型)。
参数设置:
Max Tokens:拉到最高(如 32768 或 65501)。
Temperature:0.3(稍微给一点点创造力,让文章不至于太死板,但不要太高以防 JSON 乱码)。
Response Format:选择 json_object。
上下文 (CONTEXT):留空。
用户消息 (USER):
请根据以下我为你检索到的最新全网参考资料,撰写这篇深度研究报告:
{{#Tool.text#}}
系统提示词 (SYSTEM):全选复制下方内容:
# Role
你是顶尖的 AI Researcher 与科技专栏主笔。你的任务是根据用户提供的“全网搜索结果资料”,交叉比对信息,撰写一篇排版精美、逻辑严密、深度剖析的 Markdown 博客长文,并为其打上准确的元数据。
# Workflow
【步骤一:信息整合与排版规范】
1. 深度整合:不要简单地罗列搜索结果!请将其融合成一篇有引言、核心机制、应用场景、总结反思的深度文章。
2. LaTeX 规范:识别并规范所有数学/科学公式。行内公式用单 $ 包裹;行间公式用双 $$ 包裹。
3. 极简排版:使用严谨的技术文档风格,合理运用二级 `##` 和三级 `###` 标题、加粗、列表以及代码块(#)。
【步骤二:精准元数据提取】
1. 标题 (title):生成一个极其吸引人、具有极客范或深度的文章标题(20 字以内)。
2. 标签 (tags):提取 3-5 个涵盖核心技术、概念的标签数组(如 ["人工智能", "AGI", "前沿观察"])。
3. 分类 (category):从以下英文别名中,严格选择最符合的 1 个填入:
- "terminal":编程语言、算法、代码实现。
- "infrastructure":服务器、网络协议、自动化工作流、系统架构。
- "core":数学、物理、生物等底层硬核规律。
- "cybernetics":电子电路、机械、单片机等硬件实体。
- "protocols":外语学习、社科人文阅读、沟通方法论。
- "runtime":个人日志、运动、生活反思、学习规划。
- "projects":成品展示、独立开发作品集。
【步骤三:扁平 JSON 输出(致命要求)】
请严格按照下方极简的 JSON 格式输出,绝对不要包含任何多余的嵌套字段,不要输出
{
"title": "你生成的标题",
"category": "你选择的单个分类别名",
"tags": ["标签1", "标签2", "标签3"],
"content": "你撰写并排版后的纯 Markdown 深度长文"
}
节点 4:代码执行 (装甲数据中枢)
直接复用我们跑通的终极代码,将扁平 JSON 打包为 Halo 的复杂 API Payload。
输入变量:添加
llm_json,值选择上一层 {{#LLM.text#}}。
输出变量:添加 final_body,类型选择 String。
代码语言:Python 3。
代码内容:将下方代码完整粘贴(这正是解决了 excerpt 和 pinned 报错的最终完美版):
import json
import time
import re
def render_markdown_to_html(md_str):
if not md_str: return ""
code_blocks = []
def save_code_block(match):
code_content = match.group(2)
first_line = code_content.split('\n')[0].strip()
lang_match = re.match(r'^([a-zA-Z0-9\+\-\#]+)', first_line)
lang = lang_match.group(1).lower() if lang_match else 'plaintext'
actual_code = '\n'.join(code_content.split('\n')[1:]) if '\n' in code_content else ''
code_blocks.append((lang, actual_code))
return f"__CODE_BLOCK_{len(code_blocks)-1}__"
text = re.sub(r'(math_blocks = []
def save_math_block(match):
math_blocks.append(match.group(0))
return f"__MATH_BLOCK_{len(math_blocks)-1}__"
text = re.sub(r'\$\$.?\$\$', save_math_block, text, flags=re.DOTALL)
text = re.sub(r'\$.*?\$', save_math_block, text)
text = re.sub(r'!\[([^\]])\]\(([^)]+)\)', r'', text)
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1', text)
text = re.sub(r'\\([^]+)\\', r'\1', text)
text = re.sub(r'\([^]+)\', r'\1', text)
text = re.sub(r'([^]+)', r'\1', text)
text = re.sub(r'^###\s+(.)$', r'
\1
', text, flags=re.MULTILINE)
text = re.sub(r'^##\s+(.)$', r'
\1
', text, flags=re.MULTILINE)
text = re.sub(r'^#\s+(.)$', r'
\1
', text, flags=re.MULTILINE)
text = re.sub(r'^>\s+(.)$', r'
\1
', text, flags=re.MULTILINE)
text = re.sub(r'^[-+]\s+(.)$', r'
\1 ', text, flags=re.MULTILINE)
html_lines = []
for line in text.split('\n'):
stripped = line.strip()
if stripped.startswith('<') or stripped.startswith('__CODE_BLOCK_') or stripped.startswith('__MATH_BLOCK_') or stripped.startswith(':::'):
html_lines.append(line)
elif stripped == '': html_lines.append('
')
else: html_lines.append(f"
{line}
")
html_result = '\n'.join(html_lines)
def restore_math_block(match):
idx = int(match.group(1))
return math_blocks[idx]
html_result = re.sub(r'__MATH_BLOCK_(\d+)__', restore_math_block, html_result)
def restore_code_block(match):
idx = int(match.group(1))
lang, code = code_blocks[idx]
code = code.replace('<', '<').replace('>', '>')
return f'
{code}
'
html_result = re.sub(r'__CODE_BLOCK_(\d+)__', restore_code_block, html_result)
return html_result
def main(llm_json):
if not llm_json: return {"final_body": json.dumps({"error": "无数据"}, ensure_ascii=False)}
clean_str = llm_json.strip()
clean_str = re.sub(r'^
clean_str = re.sub(r'
$', '', clean_str).strip()
title, category, tags, content = f"研究报告 {int(time.time())}", "default", [], ""
try:
data = json.loads(clean_str, strict=False)
title = data.get("title", title)
category = data.get("category", category)
tags = data.get("tags", tags)
content = data.get("content", content)
except Exception:
t_match = re.search(r'"title"\s:\s"([^"]+)"', clean_str)
if t_match: title = t_match.group(1)
c_match = re.search(r'"category"\s:\s"([^"]+)"', clean_str)
if c_match: category = c_match.group(1)
tags_match = re.search(r'"tags"\s:\s\[(.?)\]', clean_str, re.DOTALL)
if tags_match: tags = re.findall(r'"([^"]+)"', tags_match.group(1))
cnt_match = re.search(r'"content"\s:\s"(.)', clean_str, re.DOTALL)
if cnt_match:
content = cnt_match.group(1)
content = re.sub(r'"\s\}\s$','', content).replace('\\n','\n').replace('\\"','"').replace('\\\\','\\')
category_map = {
"terminal": "category-boyypubn",
"projects": "category-zhfo79vc",
"hihi": "category-k23b34gh",
"infrastructure": "category-xxxxxx",
"core": "category-xxxxxx",
"cybernetics": "category-xxxxxx",
"protocols": "category-xxxxxx",
"runtime": "category-xxxxxx",
"default": "76514a40-6ef1-4ed9-b58a-e26945bde3ca"
}
real_cat_id = category_map.get(str(category).strip().lower(), category_map["default"])
if real_cat_id.startswith("category-xxxxxx"): real_cat_id = category_map["default"]
html_content = render_markdown_to_html(content)
halo_payload = {
"apiVersion": "content.halo.run/v1alpha1",
"kind": "Post",
"metadata": {
"generateName": "post-",
"annotations": {
"content.halo.run/preferred-editor": "vditor-mde",
"content.halo.run/content-json": json.dumps({"content": html_content, "raw": content, "rawType": "markdown"}, ensure_ascii=False)
}
},
"spec": {
"title": title, "slug": f"ai-post-{int(time.time())}", "template": "","cover":"",
"deleted": False, "publish": True, "pinned": False, "allowComment": True,
"visible": "PUBLIC", "priority": 0, "excerpt": {"autoGenerate": True, "raw": ""},
"categories": [real_cat_id], "tags": tags if isinstance(tags, list) else []
}
}
return {"final_body": json.dumps(halo_payload, ensure_ascii=False)}
`
节点 5:HTTP 请求 (一键发射)
与引擎一完全一致!
Method: POST
URL: https://harris.green/apis/uc.api.content.halo.run/v1alpha1/posts
Headers:
Authorization: Bearer 你的PAT
Content-Type: application/json
Body: 选择 raw -> JSON,填入 {{#代码执行.final_body#}}`。🎉 部署完成!在右上角输入诸如“2026年AI前沿技术总结”进行测试吧!