第五章 工作流节点深度应用:构建高级信息处理流

第五章 工作流节点深度应用:构建高级信息处理流

本章我们来搭建一个实用工作流

这个工作流的整体目标是:接收用户的原始文本输入,通过“关键词提取 -> 联网搜索 -> 信息整合 -> 格式化输出”的全自动流程,最终生成一份高质量的笔记内容


5.1 大模型节点 (一):作为查询生成器的应用

我的第一步,是处理用户的原始输入。用户的输入往往是模糊的,不适合直接用于搜索。因此,我在这里使用了第一个【大模型】节点,它的核心作用是查询生成器

我为这个节点配置了如下的提示词(Prompt):

1
2
命题:{{input}}
请根据上面的命题,生成几条有针对性的搜索query,帮助用户进一步了解或验证命题的真实性。确保这些query能够涵盖相关的基本背景信息、事实依据或其他影响判断的关键信息。

image-20250803150050968

同时,为了提高工作流的透明度,我紧接着使用一个【输出】节点,将生成的 search_query 数组直接展示给用户,让他了解接下来即将执行的搜索动作。

1
2
3
4
5
#### 一、分解声明

{{search_query}}

<br>

5.2 插件节点与代码节点 (一):外部数据获取与处理

在获取到搜索查询数组后,我需要从互联网获取实时、准确的资料。

在这里,我使用了官方的【搜索材料】插件节点。它的功能很直接:接收上一步生成的 Array<String> 作为输入,然后为数组中的每一个查询执行一次联网搜索,最后将所有结果汇总成一个结构复杂的 Array<Object> 输出。

image-20250803150213062

这个插件返回的原始数据是无法直接使用的,它包含了大量的元数据和非必需信息。因此,下一步我必须对这些数据进行清洗和格式化。这个任务,我交给了【代码】节点。

我为这个【代码】节点选择了 Python 环境,并编写了以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async def main(args: Args) -> Output:
params = args.params
outputList = params['outputList']
markdown_str = ""
message_count = 0
for item in outputList:
data = item.get('data', {})
doc_results = data.get('doc_results', [])

for doc in doc_results:
sitename = doc.get('sitename', '')
summary = doc.get('summary', '')
title = doc.get('title', '')
url = doc.get('url', '')

# 拼接 markdown 格式
markdown_str += f"##### {title}\n\n"
markdown_str += f"**网站**: {sitename}\n\n"
markdown_str += f"**摘要**: {summary}\n\n"
markdown_str += f"[点击这里查看详情]({url})\n\n\n"
message_count = message_count + 1
ret: Output = {
"markdown_str": markdown_str,
"message_count": message_count,
}
return ret

这个脚本的核心功能是数据转换。它遍历复杂的输入对象,提取出我们真正关心的 titlesummaryurl 等字段,并将其统一格式化为一个长字符串 markdown_str。这完美地展示了【代码】节点在处理复杂数据结构时的强大能力,此时我们就能够拿着markdown_strmessage_count节点去给用户做输出的展示,因为他已经被我们代码格式好了

1
2
3
4
5
6
7
#### 二、交叉核对信息

检索到了**{{message_count}}**个来源...

{{markdown_str}}

<br>

5.3 大模型节点 (二):作为信息整合器的应用

现在,我已经有了用户的原始问题和经过清洗的参考资料。下一步,就是将这两者进行有机的融合。

这个任务,我交给了第二个【大模型】节点。它的角色是信息整合器,是整个工作流的核心大脑。为了让它能高质量地完成任务,我设计了一套非常详尽的 Prompt,严格约束了它的行为:

image-20250803150535660

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 角色
首席内容优化师与事实核查编辑

## 核心原则
**最重要:你必须以“用户原始笔记”为绝对的主干和框架,绝不能偏离其原有的结构和意图。“参考资料”仅用于对主干内容进行补充、佐证和丰富,绝不能喧宾夺主。最终的输出,读者应该感觉是用户原文的“精修增强版”,而不是一篇全新的文章。**

## 目标
你的目标是接收一份“用户原始笔记”草稿和一堆“参考资料”,通过将参考资料中的信息巧妙地“注入”到原始笔记中,产出一篇结构与原文保持一致、但内容更详实、论证更有力、事实更准确的最终版笔记。

## 工作流
1. **通读并锚定框架**: 首先,完整阅读“用户原始笔记”,深刻理解其文章结构、段落划分、和每个部分的核心论点。这将是你后续所有工作的“锚点”,不可更改。

2. **逐段增强与填充**: 从头开始,逐一检查原始笔记的每一个段落或要点。针对你正在处理的**当前段落的主题**,去“参考资料”中寻找可以用来支撑、解释、或提供具体示例的相关信息。

3. **巧妙融入**: 将找到的补充信息,以最自然、最流畅的方式“注入”或“附加”到原始段落中。这可能是增加一句更精确的定义、一个具体的数据、一个代码示例,或者一段更深入的背景解释。目的是让原始段落更丰满,而不是完全替换它。

4. **保持与舍弃**:
- 如果原始笔记的某个部分在参考资料中找不到对应信息,则**保留该部分的原文**,可做少量必要的文字润色。
- 如果参考资料中的某些信息与原始笔记的任何部分都**完全无关**,则**果断舍弃**,绝不引入无关主题的内容。

## 限制
- **结构一致性**: 最终输出的笔记,其章节标题、段落顺序、和核心观点流,必须与“用户原始笔记”保持高度一致。
- **禁止照搬**: 禁止直接大段复制粘贴参考资料的内容。所有引用的信息都必须经过你的理解和重述。
- **观点处理**: 如果参考资料与原始笔记的观点存在冲突,可以以“值得注意的是,也有资料指出...”的方式进行补充,而不是直接否定或替换原文的观点。
- **保持焦点**: 你的输出就是最终的笔记正文,不要包含任何关于你处理过程的额外说明。

---
## 用户原始笔记 (这是必须保留的主干):
{{user_query}}

---
## 参考资料 (仅用于补充和佐证):
{{reference_markdown}}

这个 Prompt 通过设定明确的“角色”、“核心原则”、“工作流步骤”和“限制”,将一个复杂的文本融合任务,拆解成了机器可以精确执行的指令,确保了输出结果既能吸收外部资料的优点,又不会偏离用户原文的核心思想。这展示了【大模型】节点在执行复杂文本生成任务时的应用技巧。


5.4 代码节点 (二):为 API 调用封装数据

在主内容生成之后,流程的最后一步是调用一个特定的下游服务(这里以 Gemini 模型为例),进行最终的润色。而这个服务有其独特的 API 接口数据格式要求。

因此,我使用了第二个【代码】节点,但这次选择了 JavaScript 环境。它的功能不再是数据清洗,而是数据封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
async function main({ params }: { params: { original_text: string } }): Promise<{ formatted_contents: { parts: { text: string } } }> {

// 1. 定义你的“系统提示词”
const system_prompt = `
# 角色
首席内容架构师与润色专家

## 核心任务
你的任务是接收一篇包含文本、图片链接、代码、公式等多种元素的Markdown初稿,对其进行专业的重组和精细的润色以及扩写(围绕笔记原内容进行扩写内容),最终输出一篇布局合理、格式统一、文笔流畅的、可直接发布的最终版本。

## 核心处理指令

1. **顺序处理与原地修改**: 你必须从头到尾**按顺序**处理文本。保持所有元素(段落、图片、代码块等)的**原始相对顺序**。你的所有修改都应在元素的原地发生,不要移动任何元素的位置。

2. **文本润色**: 对于普通叙述性文字段落:
- 仔细审查,修正语法错误、错别字和不当的标点。
- 优化句式,让表达更流畅、更专业,确保上下文逻辑顺滑。
- 统一使用第一人称复数“我们”。


## 格式化总览
在最终输出时,请确保全文遵循以下规范:
- **结构化内容完整性**: 再次强调,确保所有的图片链接、代码块、公式和表格都以其**原始、完整的形态**出现在最终文档中。
- **标题体系**: 你的标题需要满足以下需求:
- [核心] # 第一章
- [重要] ## 1.1
- [关键] ### 1.1.1
- 尽可能少出现四级标题,若有则如:#### 1. 、2. 、3. 、4.

## 关键提醒
你的核心价值在于**提升文字品质**和**统一格式**。对于图片链接、代码、公式等结构化数据,请严格遵守“不动”原则。你的任务不是重排文档,而是优化文档和扩写文档
`;

// 2. 从上游节点获取用户本次的实际问题
const user_prompt = params.original_text;

// 3. 将系统提示词和用户输入合并成一个长文本,用分隔符隔开
const combined_text = `${system_prompt}\n\n---\n\n## 待处理文本:\n\n${user_prompt}`;

// 4. 构建最原始、最简单的 Object 结构
const partsObject = {
text: combined_text
};

const contentsObject = {
parts: partsObject
};

// 5. 将这个简单的对象作为输出
return {
formatted_contents: contentsObject
};
}

这个节点完美演示了在与外部 API 对接时,如何利用【代码】节点作为“协议转换器”,将内部数据打包成符合外部接口规范的请求体(Request Body)。


5.5 自定义插件:将工作流与外部服务连接

在上一节,我们的【代码】节点已经将所有内容,精心封装成了一个符合特定格式的 JSON 对象。现在,我们就需要创建一个“快递员”——也就是【自定义插件】,来负责将这份“包裹”真正地发送到 Google Gemini 的服务器,并取回结果。

要完成这一步,你首先需要一个 Gemini 的 API 密钥。你可以前往 Get API key | Google AI Studio 来免费获取。

我们的技术目标,就是要用 Coze 的插件功能,来复刻下面这个官方 curl 命令的全部行为:

1
2
3
4
5
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent" \
-H 'Content-Type: application/json' \
-H 'X-goog-api-key: YOUR_GEMINI_API_KEY' \
-X POST \
-d '{ "contents": [...] }'

下面,我将分步展示如何创建这个插件。

第一步:创建插件并配置授权

我先从 Coze 主界面的左侧导航栏进入“资源库”,然后选择“插件”,点击右上角的“创建插件”按钮。

在弹出的窗口中,我填入“工具名称”,例如 call_gemini,并写上简单的描述。点击确定后,进入插件的详细配置页面。

在这里,我需要配置两项核心信息:

  1. 工具路径:这里我填入 Gemini API 的确切地址。根据我的实践,这里我优先填写路径的前缀 https://generativelanguage.googleapis.com

接下来是关键的授权部分。我需要向下滚动页面,找到“授权”区域。

  • 授权方式:我选择 API key
  • 位置:我选择 Query,因为 Gemini 的密钥是通过请求头发送的。
  • Parameter name:我严格按照 API 文档,填入 X-goog-api-key
  • 在右侧的输入框中,我粘贴上自己申请到的 Gemini API 密钥。

完成这一步,Coze 就知道要往哪里发请求,并且知道该如何验证身份了,如图文所示

image-20250803153909588

第二步:配置输入参数

接下来,我需要定义这个插件要接收哪些参数,也就是请求体(Request Body)和请求头(Header)的结构,我们创建完插件之后可以新建一个工具,进入工具的详情页来详细配置输入参数

我找到“配置输入参数”模块。根据 Gemini 的 API 文档和我们上一步代码节点的输出,我知道需要发送的数据包含 Content-Typecontents 两部分。

  1. 配置 Content-Type:

    • 我点击“+ 新增参数”,创建一个名为 Content-Type 的参数。
    • 它的“传入方法”我设置为 Header
    • 在它的默认值里,我填上 application/json
  2. 配置 contents:

    • 我再次点击“+ 新增参数”,创建 contents 参数。
    • 它的“传入方法”我设置为 Body
    • 它的参数类型是 Object,因为 contents 本身是一个包含了 parts 数组的对象。

手动一层层地创建 partstext 会比较繁琐。这里我推荐一个高效的做法:点击右上角的 “自动优化” 按钮。Coze 会尝试读取 API 的信息并自动帮我们生成参数结构。优化后,我会仔细检查生成的结构,确保它和 API 文档要求的 contents -> parts -> text 的嵌套结构完全一致。

第三步:配置输出参数

定义了如何“发”,还要定义如何“收”。我需要告诉 Coze 如何解析 Gemini 返回的 JSON 数据,并从中提取出我们想要的结果。

我找到“配置输出参数”模块。同样,最快的方法是先用“调试”功能成功发送一次请求,然后点击 “自动解析”。Coze 会根据真实的返回数据,自动帮我们构建出输出参数的结构。

image-20250803154224962

根据 Gemini 的返回格式,我知道我们需要的答案文本,位于 candidates 数组的第一个元素的 content.parts[0].text 路径下。自动解析后,我会检查并确保这个层级结构是正确的。

第四步:发布插件

当输入和输出都配置无误后,这个插件本身就已经开发完成了。我点击页面右上角蓝色的 “发布” 按钮,让它正式生效。

image-20250803154258481

只有发布之后,这个名为 call_gemini 的插件,才能在我们的工作流画布中被搜索到并使用。

至此,我们就成功地将一个外部的、强大的 AI 服务,封装成了 Coze 工作流里的一个可即插即用的“零件”。现在,我们可以回到之前的工作流中,用这个刚刚出炉的【自定义插件】节点,来完成整个流程的最后一步了,最后一步我们将上一个代码节点总结的内容与一个固定的请求投发送给gemini,他就能够产出我们想要的文本教程了

image-20250803154454904