评论上下文拉取
⚠️ 数据平面已脚本化:本 Skill 的 MCP 调用执行入口已切换为 skills/comment-context-fetcher/scripts/fetch_payload.py(拉取+分类+去重+并行,单次 run 内按 id 去重、不做跨 run 磁盘缓存,并在拉取完成后一并组装精简 contexts.json / comments_brief.json),大 JSON 落盘不经过 LLM。本文档的分类/去重规则、get_process_list 固定参数(index=1/size=5/platform_version=3/status=1/publish_status=-1)、上下文组合契约(project=项目详情+最近5条进展,process=进展详情+所属项目详情)仍为权威定义,脚本按其实现。
🔑 执行前置(Token 全局缓存优先,对齐 invoice-expert 约定):fetch_payload.py 读写全局缓存 ~/.workbuddy/.gongyi_token(跨专家共享,token 内含 _prod_/_test_ 环境段天然隔离测试/正式环境)——不传 --token 时脚本自动读缓存,有就直接用、不用每次调 get_mcp_token;本地不判断过期时间,过期以接口鉴权失败为准。脚本打印 {"need_refresh": true, ...}(退出码 3 无缓存 / 4 鉴权失败已自动清缓存)时,调用 MCP 工具 get_mcp_token(携带 caller_expert_id="comment-assistant",一次调用完成,禁止先无参调用再重试)获取新 token,以 --token 重跑(脚本同步写回缓存)。token 不打印。详见 agents/comment-assistant.md Step 0。
概述
本 Skill 负责拉取评论关联的项目或进展的公开信息,为 AI 生成建议回复提供事实依据。
核心功能:
- 根据
object_type 调用对应 MCP 工具
project → get_project_detail(项目详情)+ 追加 get_process_list(该项目最近 5 条进展):产品要求项目评论的回复信息来源 = 项目信息 + 最近 5 条进展(最多 5 条;没有进展则只用项目信息)。固定参数 index=1, size=5, platform_version=3, status=1, publish_status=-1,与项目详情并行拉取
process → get_process_detail(进展详情)+ 追加 get_project_detail(进展所属项目详情):进展评论逻辑保持不变,回复需结合「进展内容 + 所属项目信息」,项目 ID 直接取评论自带的 project_id(无需等进展拉完再取),与进展拉取并行
- 支持批量并行拉取;单次 run 内按 id 去重(同一 id 仅拉一次),不做跨 run 磁盘缓存(每次 run 实时拉取)
关键约束:
- 只读取已发布的、可追溯的信息
- 拉取失败时降级为
"context": null,不阻断流程
- 去重粒度为「
object_type + object_id」:同一类型的同一个 object_id 单次 run 内仅拉取一次;不同类型允许相同 object_id 各自拉取(project 与 process 是不同命名空间)
- process 的所属项目按 id 去重:进展评论自带的
project_id 收集后按 id 去重,同一 project_id 单次 run 内仅拉一次 get_project_detail(与 project 组合并去重,避免同一项目被 project 评论与 process 评论重复拉取)
- project 的进展列表按 id 去重:同一
project_id 单次 run 内仅拉一次 get_process_list(10 条 project 评论属同一项目时只拉 1 次)
- 不做跨 run 磁盘缓存:以上去重仅单次 run 内有效,每次 run 都实时拉取,保证不读到陈旧数据
- 并行拉取:project 组(项目详情 + 进展列表)、process 组、process 所属项目组之间、同组内多个 id 之间均并行发起调用,互不串行等待
触发场景
Agent 在 Phase 3(拉取上下文)时加载本 Skill。
输入格式
{
"items": [
{
"object_type": "project",
"object_id": "224328"
},
{
"object_type": "process",
"object_id": "652964",
"project_id": "224328"
}
]
}
| 字段 |
类型 |
必填 |
说明 |
items |
array |
是 |
待拉取上下文的列表 |
items[].object_type |
string |
是 |
对象类型:project / process |
items[].object_id |
string |
是 |
对象 ID |
items[].project_id |
string |
process 必填 |
所属项目 ID(直接取评论 OrgCommentItem.project_id;object_type=process 时用于追加拉取所属项目详情,project 类型忽略) |
工作流程
- 分类:按
object_type 分为 project / process 两组
- 组内去重:每组内按
object_id 去重,同一类型的同一个 object_id 单次 run 内只保留一条;不同类型不去重(命名空间不同)
- 收集 process 所属项目:对 process 组每条评论取自带
project_id,汇总后按 project_id 去重(与 project 组的 project_id 合并去重——同一项目若既有 project 评论又有 process 评论,单次 run 内仅拉一次)
- 并行批量调用(各类并行发起,同组内多个 id 也并行):
project 组 → 并行调用 get_project_detail(按 object_id 去重)
project 组 → 并行调用 get_process_list(按 project_id 去重,固定参数 index=1, size=5, platform_version=3, status=1, publish_status=-1,取该项目最近 5 条进展)
process 组 → 并行调用 get_process_detail(按 object_id 去重)
- process 所属项目组 → 并行调用
get_project_detail(按 project_id 去重,与 project 组合并去重)
- 组装结果:两段式结构——
projects 按 project_id 单独存放项目数据(project_detail 含基础字段 + 项目背景/爱心故事 + 募捐信息/执行地(名称)/生效备案号预算;process_list 仅 project 类型留言所属项目有),contexts 按 object_type:object_id 复合键存放各留言对象上下文并以 project_id 引用 projects(避免跨类型 ID 碰撞互相覆盖,也避免同一项目数据在多条进展上下文中重复内嵌):
- project 类型上下文 =
{"type": "project", "project_id": "<pid>"},项目数据取 projects[pid](project_detail + process_list 最近 5 条,无进展/拉取失败为空数组)
- process 类型上下文 =
{"type": "process", "process_detail": {...}, "project_id": "<pid>"},所属项目详情取 projects[pid].project_detail
输出格式
{
"projects": {
"224328": {
"project_detail": {
"project_name": "春蕾计划她们想上学",
"project_intro": "...",
"project_type": 1,
"fundras_filing_code": "...",
"closing_date": "2026-12-31",
"close_fundraising_time": "2026-12-31 23:59:59",
"project_backdrop_title": "项目背景",
"project_backdrop": "项目背景正文(已剥离 HTML)...",
"love_story_list": [
{"story_name": "...", "story_intro": "故事正文(已剥离 HTML)...", "story_summary": "..."}
],
"fundras_cycle_start_time": "2026-01-01",
"fundras_cycle_end_time": "2026-12-31",
"beneficiaries": "偏远学校",
"assisted_materials": "艺术课",
"assisted_materials_unit": "节",
"executor_site": [
{"province_name": "北京市", "city_name": "北京市", "area_name": "东城区"}
],
"filing_budget": {
"fundras_target": "500.00",
"budget_list": [
{"cost_item_one_name": "...", "cost_item_two_name": "...", "execution_content": "...", "amount_desc": "..."}
]
}
},
"process_list": [
{
"id": 1001,
"content_title": "2026年7月进展报告",
"desc": "本月完成...",
"publish_time": 1786000000
}
]
}
},
"contexts": {
"project:224328": {
"type": "project",
"project_id": "224328"
},
"process:652964": {
"type": "process",
"process_detail": {
"content_title": "项目进展报告",
"desc": "...",
"content": "...",
"concrete_info": "...",
"publish_time": "2026-08-01 10:00:00"
},
"project_id": "224328"
}
}
}
| 字段 |
类型 |
说明 |
projects |
map |
按 project_id 单独存放的项目数据(同一项目仅一份,被多条上下文引用) |
projects[pid].project_detail |
object |
项目详情:基础字段(含项目分类/资助对象名称)+ project_backdrop(_title)(项目背景)+ love_story_list(爱心故事)+ 募捐信息(fundras_cycle_start_time/fundras_cycle_end_time/beneficiaries/assisted_materials(_unit))+ executor_site(执行地,仅 province_name/city_name/area_name 三个名称字段)+ filing_budget(is_valid=1 生效备案号的筹款目标 + 预算表 4 字段),富文本已剥离 HTML;字段明细以 get_project_detail.md 为权威定义 |
projects[pid].process_list |
array |
仅 project 类型留言所属项目有:该项目最近 5 条进展(最多 5 条;无进展/拉取失败时为空数组) |
contexts |
map |
按 object_type:object_id 复合键映射的上下文(避免跨类型 ID 碰撞互相覆盖) |
contexts[key].type |
string |
上下文类型:project / process |
contexts[key].project_id |
string |
引用 projects 的键:type=project 时为该项目自身;type=process 时为该进展所属项目(取评论自带 project_id) |
contexts[key].process_detail |
object |
进展详情(type=process 时) |
异常处理
| 场景 |
处理方式 |
| MCP 调用失败 |
该 object_id 的上下文设为 null |
| 项目/进展不存在 |
该 object_id 的上下文设为 null |
| 部分失败 |
成功的正常返回,失败的设为 null |
依赖
- MCP Server:
gongyi-open-mcp
- MCP Tools:
get_project_detail, get_process_detail, get_process_list
参考文档
- get_project_detail.md
- get_process_detail.md
- get_process_list.md
- mcp-tool-mapping.md
1---2name: comment-context-fetcher3description: 拉取评论关联的项目或进展上下文。按 object_type 分类:project → get_project_detail(项目详情)+ get_process_list(该项目最近5条进展,无进展则仅项目详情);process → get_process_detail(该条进展),并按评论自带的 project_id 追加拉取所属项目详情。单次 run 内按 id 去重(同一 id 仅拉一次),去重后全部并行请求;不做跨 run 磁盘缓存。4---5
6# 评论上下文拉取
7
8> **⚠️ 数据平面已脚本化**:本 Skill 的 MCP 调用执行入口已切换为 `skills/comment-context-fetcher/scripts/fetch_payload.py`(拉取+分类+去重+并行,单次 run 内按 id 去重、不做跨 run 磁盘缓存,并在拉取完成后一并组装精简 contexts.json / comments_brief.json),大 JSON 落盘不经过 LLM。本文档的**分类/去重规则、`get_process_list` 固定参数(index=1/size=5/platform_version=3/status=1/publish_status=-1)、上下文组合契约(project=项目详情+最近5条进展,process=进展详情+所属项目详情)仍为权威定义**,脚本按其实现。
9>
10> **🔑 执行前置(Token 全局缓存优先,对齐 invoice-expert 约定)**:`fetch_payload.py` 读写全局缓存 `~/.workbuddy/.gongyi_token`(跨专家共享,token 内含 `_prod_`/`_test_` 环境段天然隔离测试/正式环境)——**不传 `--token` 时脚本自动读缓存,有就直接用、不用每次调 `get_mcp_token`**;本地不判断过期时间,过期以接口鉴权失败为准。脚本打印 `{"need_refresh": true, ...}`(退出码 3 无缓存 / 4 鉴权失败已自动清缓存)时,调用 MCP 工具 `get_mcp_token`(携带 `caller_expert_id="comment-assistant"`,一次调用完成,禁止先无参调用再重试)获取新 token,以 `--token` 重跑(脚本同步写回缓存)。token 不打印。详见 `agents/comment-assistant.md` Step 0。
11
12## 概述
13
14本 Skill 负责拉取评论关联的项目或进展的公开信息,为 AI 生成建议回复提供事实依据。
15
16**核心功能**:
17- 根据 `object_type` 调用对应 MCP 工具
18- `project` → `get_project_detail`(项目详情)**+ 追加 `get_process_list`(该项目最近 5 条进展)**:产品要求项目评论的回复信息来源 = 项目信息 + 最近 5 条进展(最多 5 条;没有进展则只用项目信息)。固定参数 `index=1, size=5, platform_version=3, status=1, publish_status=-1`,与项目详情并行拉取
19- `process` → `get_process_detail`(进展详情)**+ 追加 `get_project_detail`(进展所属项目详情)**:进展评论逻辑保持不变,回复需结合「进展内容 + 所属项目信息」,项目 ID 直接取评论自带的 `project_id`(无需等进展拉完再取),与进展拉取并行
20- 支持批量并行拉取;**单次 run 内按 id 去重**(同一 id 仅拉一次),**不做跨 run 磁盘缓存**(每次 run 实时拉取)
21
22**关键约束**:
23- 只读取已发布的、可追溯的信息
24- 拉取失败时降级为 `"context": null`,不阻断流程
25- 去重粒度为「`object_type` + `object_id`」:**同一类型的同一个 `object_id` 单次 run 内仅拉取一次**;不同类型允许相同 `object_id` 各自拉取(project 与 process 是不同命名空间)
26- **process 的所属项目按 id 去重**:进展评论自带的 `project_id` 收集后按 id 去重,同一 `project_id` 单次 run 内仅拉一次 `get_project_detail`(与 project 组合并去重,避免同一项目被 project 评论与 process 评论重复拉取)
27- **project 的进展列表按 id 去重**:同一 `project_id` 单次 run 内仅拉一次 `get_process_list`(10 条 project 评论属同一项目时只拉 1 次)
28- **不做跨 run 磁盘缓存**:以上去重仅单次 run 内有效,每次 run 都实时拉取,保证不读到陈旧数据
29- **并行拉取**:project 组(项目详情 + 进展列表)、process 组、process 所属项目组之间、同组内多个 id 之间均并行发起调用,互不串行等待
30
31## 触发场景
32
33Agent 在 Phase 3(拉取上下文)时加载本 Skill。
34
35## 输入格式
36
37```json
38{
39 "items": [
40 {
41 "object_type": "project",
42 "object_id": "224328"
43 },
44 {
45 "object_type": "process",
46 "object_id": "652964",
47 "project_id": "224328"
48 }
49 ]
50}
51```
52
53| 字段 | 类型 | 必填 | 说明 |
54|------|------|------|------|
55| `items` | array | 是 | 待拉取上下文的列表 |
56| `items[].object_type` | string | 是 | 对象类型:`project` / `process` |
57| `items[].object_id` | string | 是 | 对象 ID |
58| `items[].project_id` | string | process 必填 | 所属项目 ID(直接取评论 `OrgCommentItem.project_id`;object_type=process 时用于追加拉取所属项目详情,project 类型忽略) |
59
60## 工作流程
61
621. **分类**:按 `object_type` 分为 project / process 两组
632. **组内去重**:每组内按 `object_id` 去重,同一类型的同一个 `object_id` 单次 run 内只保留一条;不同类型不去重(命名空间不同)
643. **收集 process 所属项目**:对 process 组每条评论取自带 `project_id`,汇总后**按 `project_id` 去重**(与 project 组的 project_id 合并去重——同一项目若既有 project 评论又有 process 评论,单次 run 内仅拉一次)
654. **并行批量调用**(各类并行发起,同组内多个 id 也并行):
66 - `project` 组 → 并行调用 `get_project_detail`(按 object_id 去重)
67 - `project` 组 → 并行调用 `get_process_list`(按 project_id 去重,固定参数 `index=1, size=5, platform_version=3, status=1, publish_status=-1`,取该项目最近 5 条进展)
68 - `process` 组 → 并行调用 `get_process_detail`(按 object_id 去重)
69 - process 所属项目组 → 并行调用 `get_project_detail`(按 project_id 去重,与 project 组合并去重)
704. **组装结果**:两段式结构——`projects` 按 project_id 单独存放项目数据(`project_detail` 含基础字段 + 项目背景/爱心故事 + 募捐信息/执行地(名称)/生效备案号预算;`process_list` 仅 project 类型留言所属项目有),`contexts` 按 `object_type:object_id` 复合键存放各留言对象上下文并以 `project_id` 引用 `projects`(避免跨类型 ID 碰撞互相覆盖,也避免同一项目数据在多条进展上下文中重复内嵌):
71 - **project 类型**上下文 = `{"type": "project", "project_id": "<pid>"}`,项目数据取 `projects[pid]`(`project_detail` + `process_list` 最近 5 条,无进展/拉取失败为空数组)
72 - **process 类型**上下文 = `{"type": "process", "process_detail": {...}, "project_id": "<pid>"}`,所属项目详情取 `projects[pid].project_detail`
73
74## 输出格式
75
76```json
77{
78 "projects": {
79 "224328": {
80 "project_detail": {
81 "project_name": "春蕾计划她们想上学",
82 "project_intro": "...",
83 "project_type": 1,
84 "fundras_filing_code": "...",
85 "closing_date": "2026-12-31",
86 "close_fundraising_time": "2026-12-31 23:59:59",
87 "project_backdrop_title": "项目背景",
88 "project_backdrop": "项目背景正文(已剥离 HTML)...",
89 "love_story_list": [
90 {"story_name": "...", "story_intro": "故事正文(已剥离 HTML)...", "story_summary": "..."}
91 ],
92 "fundras_cycle_start_time": "2026-01-01",
93 "fundras_cycle_end_time": "2026-12-31",
94 "beneficiaries": "偏远学校",
95 "assisted_materials": "艺术课",
96 "assisted_materials_unit": "节",
97 "executor_site": [
98 {"province_name": "北京市", "city_name": "北京市", "area_name": "东城区"}
99 ],
100 "filing_budget": {
101 "fundras_target": "500.00",
102 "budget_list": [
103 {"cost_item_one_name": "...", "cost_item_two_name": "...", "execution_content": "...", "amount_desc": "..."}
104 ]
105 }
106 },
107 "process_list": [
108 {
109 "id": 1001,
110 "content_title": "2026年7月进展报告",
111 "desc": "本月完成...",
112 "publish_time": 1786000000
113 }
114 ]
115 }
116 },
117 "contexts": {
118 "project:224328": {
119 "type": "project",
120 "project_id": "224328"
121 },
122 "process:652964": {
123 "type": "process",
124 "process_detail": {
125 "content_title": "项目进展报告",
126 "desc": "...",
127 "content": "...",
128 "concrete_info": "...",
129 "publish_time": "2026-08-01 10:00:00"
130 },
131 "project_id": "224328"
132 }
133 }
134}
135```
136
137| 字段 | 类型 | 说明 |
138|------|------|------|
139| `projects` | map | 按 project_id 单独存放的项目数据(同一项目仅一份,被多条上下文引用) |
140| `projects[pid].project_detail` | object | 项目详情:基础字段(含项目分类/资助对象名称)+ `project_backdrop(_title)`(项目背景)+ `love_story_list`(爱心故事)+ 募捐信息(`fundras_cycle_start_time`/`fundras_cycle_end_time`/`beneficiaries`/`assisted_materials(_unit)`)+ `executor_site`(执行地,仅 province_name/city_name/area_name 三个名称字段)+ `filing_budget`(is_valid=1 生效备案号的筹款目标 + 预算表 4 字段),富文本已剥离 HTML;字段明细以 [get_project_detail.md](references/tools/get_project_detail.md) 为权威定义 |
141| `projects[pid].process_list` | array | **仅 project 类型留言所属项目有**:该项目最近 5 条进展(最多 5 条;无进展/拉取失败时为空数组) |
142| `contexts` | map | 按 `object_type:object_id` 复合键映射的上下文(避免跨类型 ID 碰撞互相覆盖) |
143| `contexts[key].type` | string | 上下文类型:`project` / `process` |
144| `contexts[key].project_id` | string | 引用 `projects` 的键:type=project 时为该项目自身;**type=process 时为该进展所属项目**(取评论自带 project_id) |
145| `contexts[key].process_detail` | object | 进展详情(type=process 时) |
146
147## 异常处理
148
149| 场景 | 处理方式 |
150|------|---------|
151| MCP 调用失败 | 该 `object_id` 的上下文设为 `null` |
152| 项目/进展不存在 | 该 `object_id` 的上下文设为 `null` |
153| 部分失败 | 成功的正常返回,失败的设为 `null` |
154
155## 依赖
156
157- MCP Server: `gongyi-open-mcp`
158- MCP Tools: `get_project_detail`, `get_process_detail`, `get_process_list`
159
160## 参考文档
161
162- [get_project_detail.md](references/tools/get_project_detail.md)
163- [get_process_detail.md](references/tools/get_process_detail.md)
164- [get_process_list.md](references/tools/get_process_list.md)
165- [mcp-tool-mapping.md](references/mcp-tool-mapping.md)