Install
openclaw skills install @yuanzhian-patsnap/rd-direction-finder给定工程/技术问题描述,按"需求牵引-瓶颈分析-解决思路"三维解析需求,提取互斥技术难题,识别核心攻关方向,并行执行专利、论文及 Web 多平台检索,综合输出不超过 max_directions(默认 10)路结构化研发方向(含核心问题、研发内容、技术目标、代表参考证据),并按固定骨架同步生成 Markdown 检索报告与可渲染 HTML 报告。
openclaw skills install @yuanzhian-patsnap/rd-direction-finder| 参数 | 说明 | 默认值 | 是否必须 |
|---|---|---|---|
problem_text | 工程/技术问题描述,100~400 字 | — | 必须 |
max_directions | 最多输出几路研发方向 | 10 | 可选 |
实际交付顺序固定为:
当前对话显示工作流:在当前对话中直接输出完整 Markdown 报告正文,方便用户阅读。Markdown 报告:保存结构化数据,写入 <MD_PATH>。payload JSON:保存结构化数据,写入 <PAYLOAD_PATH>。HTML 报告:由 scripts/render_report.py 读取 payload 生成,写入 <HTML_PATH>。依赖关系为:
problem_text -> 当前对话显示工作流 -> payload JSON -> HTML 报告
说明:当前对话显示的 Markdown 报告正文必须写入 payload 的 markdown_report 字段;HTML 从 payload 中的同一正文渲染,保证对话预览、HTML 页面内容一致。
路径定义见 assets/paths.md,payload schema 见 assets/payload-schema.md,报告结构见 assets/report-template.md,检索规则见 assets/workflow.md。
一次完整运行必须同时满足:
<PAYLOAD_PATH> 存在且 size > 0。<HTML_PATH> 存在且 size > 0。<MD_PATH> 存在且 size > 0。summary.cnt_case / cnt_paper / cnt_patent / cnt_web 必须等于 appendix.a1/a2/a3/a4 的实际长度。写入约束
render_report.py 生成 HTML。files 工具写入 payload,必须使用合法 JSON 参数。大 payload 使用 begin_write -> append -> finish_write:append 只能使用 begin_write 返回的 write_id,sequence 必须递增,content 大小不得超过工具返回的硬上限。files.materialize 落盘,避免将大内容重新内联到工具调用参数中。本节给出每个步骤的核心动作;判别准则、查询模板与平台对照表见 assets/workflow.md,报告骨架与字段约束见 assets/report-template.md
按"需求牵引–瓶颈分析–解决思路"三维结构,从 problem_text 中提炼关键信息,忠实原文不作主观评判:
任一子项原文未提及时,子项标题保留、值写"未提及",不得捏造。细化判别要点见 assets/workflow.md 「Step 0 三维论证细化判别要点」节;输出表格骨架以 assets/report-template.md 「一、项目需求解析」为准。
从同一份 problem_text 中提炼 N 个互斥、边界清晰的技术难题,统一编号 T1、T2…TN:
每个攻关方向独立执行:
paper.search:strategy=["semantic","keyword"],topk=20,sources=["paper"]patent.search:strategy=["semantic","keyword"],topk=20,sources=["patent"]共 k×2 次工具调用,尽量并行发起。
与 Step 2a 同步发起,按"期刊论文 / 工程案例 / 技术标准"三类各 1 条 web.search,共 3 条。三条查询串模板与平台域名对照表见 assets/workflow.md 「Step 2b 并行检索 — Web 多平台补充」节。
按 assets/report-template.md 骨架顺序,将整份 Markdown 报告直接内联在最终回复正文里。
完整输出标准:
报告必须包含的完整结构:以 assets/report-template.md 为唯一骨架真理源,逐节核对、缺一不可。骨架顺序为:
## 顶部元信息块
报告正文必须以二级标题 `## 科研需求检索报告` 开头,紧跟一个 blockquote 作为元信息块;元信息 blockquote 之后再用独立 blockquote 承载"需求输入原文"(详见下一节),随后才进入正文第一节"一、项目需求解析":
> **科研需求检索报告**
> **项目名称:** {{project_name}}
>
> **申报单位:** {{applicant}} **检索日期:** {{today_iso}} **检索范围:** 专利+论文+网络学术文献补充
---
渲染规则:
- `{{project_name}}` 为空或未识别时,**不输出该整行**(不要写 `项目名称:未提及`,整条删除即可)。
- `{{applicant}}` 为空或未识别时,**不输出该整行**(不要写 `申报单位:未提及`,整条删除即可)。
- `检索日期` 和 `检索范围` 始终显示。
- `{{today_iso}}` 取报告生成当天,格式 `YYYY-MM-DD`。
## 需求输入原文
> **需求输入原文:**
>
> {{requirement_text}}
## 一、项目需求解析
| 维度 | 关键内容 |
| :----------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **需求牵引:问题从哪来** | **场景**:<具体工程场景> <br> **痛点**:<安全威胁或重大损失> <br> **当前应对**:<临时或权宜措施> |
| **瓶颈分析:现有方案为何不行** | **瓶颈**:<能力极限+数据> <br> **现有措施局限**:<代价是什么> <br> **原理局限**:<传统技术路线无法突破的本质> |
| **解决思路:本课题如何突破** | **技术路径**:<核心新思路> <br> **系统方案**:<全链条构想> <br> **工程兼容性**(若有):<接口/运维兼容性> <br> **锁定目标**:<对应工程痛点的解决方案> |
## 二、技术难题拆解
| 序号 | 技术难题描述 |
| :--: | :--------------------------------------------- |
| T1 | **[难题名称]**:<一句话概括,引用原文关键表述> |
| T2 | **[难题名称]**:<一句话概括,引用原文关键表述> |
| ... | ... |
> 基于以上 N 个技术难题,识别 k 个核心技术攻关方向(k = min(N, max_directions),可合并同类项),驱动 Step 2a、Step 2b 检索:
>
> - **方向①** <名称>(覆盖 T1[+T2])
> - **方向②** <名称>(覆盖 T3)
> - ...
## 三、主要研究内容和措施
### 3.1 检索成果汇总
**检索成果汇总**:结论前置,一句话总结检索成果。本次技术经检索后发现 {{cnt_case}} 条工程案例/标准、{{cnt_paper}} 篇相似技术论文,{{cnt_patent}} 篇相似技术专利,{{cnt_web}} 条网络学术补充条目,业内主要研究该技术单位 {{cnt_org_total}} 家,其中代表性单位为 {{top_orgs}}。
> 总数为各路线去重合并后的全量计数,必须来自工具返回的真实数字;禁止编造。各路线明细在 3.2 起逐条展开,完整清单见附录 A1/A2/A3/A4,主要科研单位见第四节。
>
> 渲染规则:
>
> - `{{cnt_org_total}}` 是按归一化后单位名去重的全量家数;`{{cnt_org_top5}}` 是第四节实际展示的家数(≤5)。两者一般不相等,禁止合并填同一个数。
> - `{{top_orgs}}` 取第四节"主要科研单位分析"前 2–3 家归一化后的单位名,以中文顿号"、"分隔;不足 2 家时照实列出,0 家时输出"暂未识别明确研究单位",整段总结仍保留。
每条路线一个小节(3.2 / 3.3 / 3.4 / ...)
### 3.2 研发路线一:{{dir1_name}}
**检索结果**:本路线命中 {{dir1_cnt_case}} 条工程案例/标准、{{dir1_cnt_paper}} 篇论文、{{dir1_cnt_patent}} 篇专利、{{dir1_cnt_web}} 条网络补充、时间分布、主要申请人 TOP3、核心技术分支;若 0 命中:写"未检索到直接相关技术"
**核心问题**:{{dir1_problem}}
**研发内容**(每条至少绑定 1 个 [S#] 证据):
- {{dir1_content1}} [S#]
- {{dir1_content2}} [S#]
- {{dir1_content3}} [S#]
- {{dir1_content4}} [S#]
**技术目标**:{{dir1_target}}
**代表参考证据(工程案例/标准)**: 表格列:`{{源}} | {{年}} | {{发布机构}} | [{{标题}}]({{url}}) | {{核心内容}} [S#] | {{鉴定等级}}`
**代表参考证据(论文)**:表格列:`{{源}} | {{年}} | {{作者机构}} | [{{标题}}]({{url}}) | 被引 {{n}} | {{DOI}} [S#]`
**代表参考证据(专利)**: 表格列:`{{源}} | {{公开号}} | [{{标题}}]({{url}}) | {{申请人}} | {{公开年}} | {{状态}} | 被引 {{n}} [S#]`
**代表参考证据(Web 学术补充)**: 表格列:`{{源站点}} | [{{标题}}]({{url}}) | {{简述}} [S#]`
> 工程案例/标准/论文/专利/Web 学术补充条目均必须带 [S#],与附录 A1/A2/A3/A4 一一对应。每路 3–5 条代表证据;完整 ≤10 条清单移至附录。
### 3.(k+1) 研发路线 {{k}}:{{dirk_name}}(格式同 3.2)
(同上结构,按方向数重复)
### 3.(k+2) 研发路线总结
| 路线 | 对应难题 | 核心攻关 | 输出成果 | 代表参考证据 |
| ----- | -------- | -------- | -------- | ------------ |
| 方向① | T1 | … | … | [S#] |
| 方向② | T2 | … | … | [S#] |
## 四、主要科研单位分析
| 单位 | 覆盖路线 | 技术重点 | 代表成果 |
| -------- | ------------- | -------- | -------- |
| {{org1}} | {{dir1_name}} | … | [S#] |
## 五、附录
### A1 工程案例 / 标准清单
- 按路线分组,表格列:`{{编号}} | {{源}} | {{年}} | {{发布机构}} | [{{标题}}]({{url}}) | {{核心内容}} [S#] | {{鉴定等级}}`
### A2 论文清单
- 按路线分组,表格列:`{{源}} | {{年}} | {{作者机构}} | [{{标题}}]({{url}}) | 被引 {{n}} | {{DOI}} [S#]`
### A3 专利清单
- 按路线分组,表格列:`{{源}} | {{公开号}} | [{{标题}}]({{url}}) | {{申请人}} | {{公开年}} | {{状态}} | 被引 {{n}} [S#]`
### A4 网络学术清单
- 按"期刊论文 / 工程案例 / 技术标准"三类归并(与 Step 2b 三条查询串对应),表格列:`{{源站点}} | [{{标题}}]({{url}}) | {{简述}} [S#]`
Step 4.1 完成的唯一判定标准:对话内可见完整 Markdown 报告,包含从"科研需求检索报告"到附录 A4 的全部章节内容。
在 Step 4.1 完成后的同一轮回复内,将完整的 Markdown 报告正文写入 $MD_PATH
REPORTS_DIR="reports"
DATE=$(date +%Y%m%d)
MD_PATH="@session/reports/rd-direction-finder-report-${DATE}.md"
mkdir -p "$REPORTS_DIR"
# Python 一次性写入(避免 heredoc 占位符问题)
python3 << 'PYEOF'
import os, sys
from pathlib import Path
md_path = Path(os.environ["MD_PATH"])
md_path.parent.mkdir(parents=True, exist_ok=True)
# markdown_text = Step 4 对话中生成的完整报告正文
markdown_text = os.environ.get("_STEP4_MARKDOWN", "") or sys.stdin.read()
if markdown_text:
md_path.write_text(markdown_text, encoding="utf-8")
print(f"[ok] markdown -> {md_path} ({md_path.stat().st_size} bytes)")
else:
print("[warn] stdin empty - Step 5 will auto-rebuild")
PYEOF
Step 4.2 完成的唯一判定标准:完成后执行最低自检
test -s "$MD_PATH" :Markdown 报告非空(> 0 bytes)Step 4.2 完成后,再启动 step5,同一轮回复内完成 HTML 报告的落盘;HTML 报告必须包含从"科研需求检索报告"到附录 A4 的全部章节内容,缺一视为交付失败。
HTML 直接生成是默认交付产物,无需用户额外请求。
#!/bin/bash
# 路径定义(相对当前工作目录)
REPORTS_DIR="reports"
DATE=$(date +%Y%m%d)
PAYLOAD_PATH="@session/reports/rd-direction-finder-payload-${DATE}.json"
HTML_PATH="@session/reports/rd-direction-finder-report-${DATE}.html"
mkdir -p "$REPORTS_DIR"
# 导出到环境,供后续 Python heredoc 读取
export MD_PATH PAYLOAD_PATH HTML_PATH
核心原则:在本地 shell 中运行 Python,将完整的结构化 dict 一次性序列化到磁盘。禁止分块追加。
数据准备:Step 0–4 生成的全部结构化数据,通过 shell 环境变量传入 Python。以下变量名与 payload schema 字段一一对应:
| 环境变量 | 对应 payload 字段 | 内容说明 | 必填 |
|---|---|---|---|
META_JSON | meta | JSON 字符串:{"today_iso": "YYYY-MM-DD", "project_name": "...", "applicant": "..."} | 是 |
REQUIREMENT_TEXT | requirement_text | 原始需求文本(纯文本) | 是 |
ANALYSIS_JSON | analysis | JSON 字符串:{"demand": {"scene": "...", "pain": "...", "current": "..."}, "bottleneck": {"limit": "...", "cost": "...", "principle": "..."}, "solution": {"path": "...", "system": "...", "compat": "...", "target": "..."}} | 是 |
ISSUES_JSON | issues | JSON 字符串:[{"no": "T1", "name": "...", "desc": "..."}] | 是 |
DIRECTIONS_JSON | directions | JSON 字符串(含 evidence),见下表 | 是 |
SUMMARY_JSON | summary | JSON 字符串:{"cnt_case": N, "cnt_paper": N, "cnt_patent": N, "cnt_web": N, "cnt_org_total": N, "cnt_org_top5": N, "top_orgs": "..."} | 是 |
UNITS_JSON | units | JSON 字符串:[{"name": "...", "covers": ["..."], "focus": "...", "achievements": "...", "cites": ["S#"]}] | 是 |
DIRECTIONS_JSON 中每个方向对象的必填结构:
{
"name": "方向名称",
"covers": ["T1"],
"problem": "核心问题描述",
"contents": [{ "text": "研发内容", "cites": ["S1"] }],
"target": "技术目标",
"counts": { "case": 1, "paper": 2, "patent": 1, "web": 0 },
"evidence": {
"cases": [
{
"no": "C1",
"src": "...",
"year": "...",
"publisher": "...",
"title": "...",
"url": "...",
"summary": "...",
"grade": "...",
"sid": "S1"
}
],
"papers": [
{
"src": "...",
"year": "...",
"affiliation": "...",
"title": "...",
"url": "...",
"cited_by": "N",
"doi": "...",
"sid": "S2"
}
],
"patents": [
{
"src": "...",
"pub_no": "...",
"title": "...",
"url": "...",
"applicant": "...",
"pub_year": "...",
"status": "...",
"cited_by": "N",
"sid": "S3"
}
],
"webs": [
{
"site": "...",
"title": "...",
"url": "...",
"summary": "...",
"sid": "S4",
"category": "期刊论文"
}
]
},
"summary_focus": "总结表核心攻关列",
"summary_output": "总结表输出成果列",
"summary_sids": ["S1", "S2"]
}
markdown_report 处理(关键):
markdown_report 字段不能通过环境变量传输(多行文本含特殊字符会导致截断或转义失败)。采用以下流程:
完整执行脚本:
# ========== 第1步:设置结构化数据环境变量 ==========
export META_JSON='{"today_iso": "'$(date +%Y-%m-d)'", "project_name": "...", "applicant": "..."}'
export REQUIREMENT_TEXT='...'
export ANALYSIS_JSON='...'
export ISSUES_JSON='...'
export DIRECTIONS_JSON='...'
export SUMMARY_JSON='...'
export UNITS_JSON='...'
# ========== 第2步:执行 Python payload 构建 ==========
python3 << 'PYEOF'
import json, os
from pathlib import Path
# 从环境变量读取结构化数据
meta = json.loads(os.environ.get("META_JSON", "{}"))
meta.setdefault("source", "rd-direction-finder")
requirement_text = os.environ.get("REQUIREMENT_TEXT", "")
analysis = json.loads(os.environ.get("ANALYSIS_JSON", "{}"))
issues = json.loads(os.environ.get("ISSUES_JSON", "[]"))
directions = json.loads(os.environ.get("DIRECTIONS_JSON", "[]"))
summary = json.loads(os.environ.get("SUMMARY_JSON", "{}"))
units = json.loads(os.environ.get("UNITS_JSON", "[]"))
# 优先从 $MD_PATH 文件读取 Step 4 已写入的 Markdown 报告
md_path = Path(os.environ["MD_PATH"])
if md_path.exists() and md_path.stat().st_size > 0:
markdown_report = md_path.read_text(encoding="utf-8")
print(f"[ok] markdown loaded from {md_path}: {len(markdown_report)} chars")
else:
# 若 Step 4 未写入,自动从结构化数据重建 Markdown 报告
# 并同步写入 $MD_PATH,保证 HTML/Markdown/Payload 三一致
import sys
sys.path.insert(0, "scripts")
from render_report import build_markdown_report
payload_stub = {
"meta": meta,
"requirement_text": requirement_text,
"analysis": analysis,
"issues": issues,
"directions": directions,
"summary": summary,
"units": units,
}
markdown_report = build_markdown_report(payload_stub)
md_path.parent.mkdir(parents=True, exist_ok=True)
md_path.write_text(markdown_report, encoding="utf-8")
print(f"[ok] markdown auto-rebuilt: {len(markdown_report)} chars -> {md_path}")
# 从 directions evidence 自动构建 appendix
a1, a2, a3, a4 = [], [], [], []
case_no = 1
for d in directions:
ev = d.get("evidence", {})
dir_name = d.get("name", "")
for item in ev.get("cases", []):
row = dict(item)
row.setdefault("no", f"C{case_no}")
row["direction"] = dir_name
a1.append(row)
case_no += 1
for item in ev.get("papers", []):
row = dict(item)
row["direction"] = dir_name
a2.append(row)
for item in ev.get("patents", []):
row = dict(item)
row["direction"] = dir_name
a3.append(row)
for item in ev.get("webs", []):
row = dict(item)
row.setdefault("category", "期刊论文")
a4.append(row)
appendix = {"a1": a1, "a2": a2, "a3": a3, "a4": a4}
# 自动修正 summary 计数(以 appendix 实际长度为准)
summary["cnt_case"] = len(a1)
summary["cnt_paper"] = len(a2)
summary["cnt_patent"] = len(a3)
summary["cnt_web"] = len(a4)
if "cnt_org_total" not in summary:
summary["cnt_org_total"] = len(units)
if "cnt_org_top5" not in summary:
summary["cnt_org_top5"] = min(5, len(units))
if "top_orgs" not in summary:
names = [u.get("name", "") for u in units[:3] if u.get("name")]
summary["top_orgs"] = "、".join(names) if names else "暂未识别明确研究单位"
# 组装 payload
payload = {
"meta": meta,
"requirement_text": requirement_text,
"analysis": analysis,
"issues": issues,
"directions": directions,
"summary": summary,
"units": units,
"appendix": appendix,
"markdown_report": markdown_report
}
output = Path(os.environ["PAYLOAD_PATH"])
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(
json.dumps(payload, ensure_ascii=False, indent=2),
encoding="utf-8"
)
# 验证 markdown_report 非空
if not markdown_report.strip():
print("[warn] markdown_report is empty — HTML will be built from structured data only")
print("[warn] to guarantee consistency with dialog output, ensure .report.md is written correctly")
else:
print(f"[ok] markdown_report loaded: {len(markdown_report)} chars")
print(f"[ok] payload written -> {output.resolve()} ({output.stat().st_size} bytes)")
PYEOF
关键要求:
export VAR='value' 设置完成。markdown_report 严禁通过环境变量传输(多行文本含特殊字符会导致截断)。Python 脚本优先从 $MD_PATH 文件读取;若 Step 4 未写入该文件,则自动从结构化数据重建并写入。$MD_PATH 必须包含 Step 4 在对话中输出的完整 Markdown 报告正文(从 ## 科研需求检索报告 到附录 A4)。这是保证 HTML 与对话内容一致的唯一方式。DIRECTIONS_JSON 中的 evidence 字段必须完整包含每路方向筛选后的代表证据(cases/papers/patents/webs)。directions[].evidence 构建 appendix,并自动修正 summary 中的计数字段,确保 cnt_* 始终等于对应附录数组长度。复用 Step 5.1 已定义的 $HTML_PATH
使用 scripts/render_report.py 渲染报告,写入 HTML_PATH
预期 stdout 包含两行 [ok]:
[ok] report written -> <HTML_PATH> (... bytes)
[ -s "$PAYLOAD_PATH" ] && [ -s "$HTML_PATH" ] && [ -s "$MD_PATH" ] && echo "OK: all files ready" || echo "FAIL: missing or empty files"
输出 FAIL 视为交付失败,按"异常处理"章节处置。
| 规则编号 | 约束内容 |
|---|---|
| Q1 | 报告头部必须包含两个前置模块(缺一视为交付不合格):①元信息 blockquote 块、②需求原文 blockquote 块;两者均置于 Step 0 三维表和所有正文章节之前。具体字段、渲染规则与"未识别整行删除"逻辑以 assets/report-template.md 「顶部元信息块」「需求输入原文」两节为准,本表不重复。 |
| Q2 | Step 1 表格必须紧接 Step 0,先于研发路线输出 |
| Q3 | 技术难题编号统一使用 T1、T2…TN;攻关方向统一使用方向①②③…;[S#] 全局唯一 |
| Q4 | 攻关方向数量 k = min(N, max_directions),可合并同类项但不得遗漏任何难题 |
| Q5 | 每路研发内容不少于 3 条,且每条至少关联 1 个 [S#] 证据 |
| Q6 | 总结表必须包含"对应难题"列,明确标注每路方向覆盖的 T# 编号 |
| Q7 | 附录 A4 网络学术清单必须标注来源平台(取值范围以 assets/workflow.md 平台域名对照表为准,未列入对照表的来源标"其他");条数与归并规则见 Step 3 |
| Q8 | 未有证据支撑的结论标注 Unverified |
| Q9 | 检索数量统计必须使用工具返回去重后的真实计数(即附录行数);禁止编造或主观估算。若工具未返回可信计数,相应单元格写"未提及",不得用占位数字凑数 |
| Q10 | 原文未提及的子项填"未提及",不得捏造;忠实原文不添加主观评价 |
| Q11 | 所有单位/机构名做中英文/简称归一,全文同一主体使用同一称谓;首次出现时可在括号内给出原称 |
| 情形 | 处理方式 |
|---|---|
| 某路专利/论文检索无结果 | 注明"当前数据库未检索到直接相关专利/论文,建议参考相邻领域证据" |
| Web 平台无结果或拒绝访问 | 见 Step 3 末条(附录 A4 注明"[平台名] 本次未返回可用结果") |
| 问题描述过短(<50 字) | 提示用户补充更多背景信息后重试 |
| 难题数 N 与 max_directions 关系 | 见 Q4(按 k = min(N, max_directions) 处理,可合并同类项但不得遗漏 T#) |
| HTML 生成失败 | 在回复中说明失败原因;Markdown 正文仍须完整输出;提供用户可复现命令 |
检索工具:
mcp__patsnap_search__.patsnap_search,按每个攻关方向分别检索 sources=["paper"] 和 sources=["patent"],或在需要混合召回时使用 sources=["all"]。mcp__patent_search__.search_patents_nested、mcp__core_patents__.search_patents 或同类 patent MCP。生成工具:
python3 scripts/render_report.py --payload "$PAYLOAD_PATH" --output "$HTML_PATH" 生成 HTML 文件。本 Skill 依赖智慧芽开放平台 MCP 服务: