Harris
发布于 2026-03-20 / 11 阅读
0
0

Dify AI Researcher 引擎自动化部署指南

🚀 Dify 第三引擎 (AI Researcher) 全自动化部署指南


在这个工作流中,前半部分(搜索 + 撰写)是全新的,而后半部分(代码清洗 +HTTP 发布)将 100% 复用 我们在引擎一中打磨完美的“防弹架构”。


请在 Dify 中新建一个 工作流 (Workflow),并按以下步骤依次连接节点:


节点 1:Start (开始)


这是整个研究引擎的输入端。你需要告诉 AI 今天研究什么主题。


添加变量:点击 +
变量名:填写 topic
变量类型:选择 String (短文本)
是否必填:是

节点 2:Tool (工具节点 - 全网搜索)


让 Dify 具备联网搜索的能力。


添加节点:选择 Tool (工具)。
选择工具:推荐使用 DuckDuckGo SearchTavily Search(如果你配置了 API 的话,Google Search 也可以)。
输入参数 (Query/Search Input):点击 {x} 插入变量,选择 {{#Start.topic#}}
_(注:这个节点运行后,会输出一段包含多篇网页搜索结果、摘要和链接的 JSON 或文本。)_

节点 3:LLM (AI 研究员与撰稿人)


这是引擎的大脑,负责把零散的搜索结果融合成一篇有深度、排版精美的万字长文。


模型选择:选择 qwen3-maxkimi-k2.5(必须是长文本强逻辑模型)。
参数设置
Max Tokens:拉到最高(如 3276865501)。
Temperature0.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
代码内容:将下方代码完整粘贴(这正是解决了 excerptpinned 报错的最终完美版):
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'(
+|~~~+)(.?)\1', save_code_block, md_str, flags=re.DOTALL)

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'\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'([^]+)', 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前沿技术总结”进行测试吧!

    评论