@lark-apaas/coding-steering 0.1.12-alpha.0 → 0.1.13-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.12-alpha.0",
3
+ "version": "0.1.13-alpha.0",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,316 @@
1
+ ---
2
+ name: plugin-guide
3
+ description: AI 新版插件集成规范与 Plugin 链路指南。支持AI智能生文、AI智能生图、AI图片理解、AI文档解析、AI结构化提取、AI图片处理等。Use when 需要:(1) 创建或管理 PluginInstance 插件实例,(2) 调用 capabilityClient 生成插件调用代码,(3) 编写 capabilityClient 调用代码。触发词:插件, plugin, AI生文, AI生图, 图片理解, 文档解析, 结构化提取, capabilityClient, pluginInstance
4
+ compatibility: "requires: lark-cli with plugin commands (apps +plugin-install, +plugin-uninstall, +plugin-list). If not met, tell user: lark-cli 当前版本不支持插件能力,请升级到最新版本"
5
+ when_to_use: "lark-cli apps +plugin-* commands are available. If not, tell user: lark-cli 当前版本不支持插件能力,请升级到最新版本"
6
+ steering: true
7
+ steering-topic: plugin_guide
8
+ match-template-name: design-stack
9
+ ---
10
+
11
+ # Plugin 集成指南
12
+
13
+ AI 新版插件集成规范与 Plugin 链路指南。支持的能力包括:AI智能生文、AI智能生图、AI图片理解、AI文档解析(PDF/DOC/PPTX等)、AI结构化提取(文本→JSON、图片→JSON)、AI内容摘要、AI翻译、AI语音合成与识别、AI图片处理(抠图/换背景/风格转换)等。本文档介绍插件实例配置、调用投影的获取方式,以及 `capabilityClient` 调用入口。
14
+
15
+ ## ⚠️ 前置门禁
16
+
17
+ 本 skill 依赖 lark-cli 的插件管理能力。开始前先确认当前环境是否可用:
18
+
19
+ ```bash
20
+ lark-cli apps --help 2>&1 | grep -q '+plugin-install' && echo "READY" || echo "MISSING"
21
+ ```
22
+
23
+ - **READY** → 继续
24
+ - **MISSING** → 当前 lark-cli 版本不支持插件命令,请更新到最新版后重试:
25
+
26
+ ## ⚠️ 铁律:禁止 Mock 插件调用
27
+
28
+ **当 AGENTS.md 包含「插件规划」时,所有规划的插件必须创建实例并通过 `capabilityClient` 真实调用。**
29
+
30
+ | 禁止 | 正确 |
31
+ |------|------|
32
+ | ❌ `setTimeout` + 硬编码数据模拟 AI 返回 | ✅ `capabilityClient.load(id).call(actionKey, input)` |
33
+ | ❌ `picsum.photos` / 随机图片替代 AI 生图 | ✅ `capabilityClient.load(id).call('textToImage', {...})` |
34
+ | ❌ 注释掉 `capabilityClient` import 用 Mock 函数 | ✅ 真实 import 并调用 |
35
+ | ❌ AGENTS.md 规划了插件但 `server/capabilities/` 为空 | ✅ 每个规划的插件都有对应 JSON 文件 |
36
+
37
+ > **与 Mock 展示数据的区别**:design_agent prompt 中的 "Mock 数据仅用于无附件的 prototype" 指的是表格示例行、卡片占位文本等**展示数据**。AI 生文/生图/翻译/结构化等**插件能力不属于 Mock 数据范围**,必须真实调用。
38
+
39
+ ## Quick Reference
40
+
41
+ | 操作 | 方式 |
42
+ |------|------|
43
+ | 插件包管理(安装/卸载/查看) | 由 lark-apps skill 引导,参考其 plugin reference 文档 |
44
+ | 创建实例 | 设计 paramsSchema/formValue → 自查规则 → 写入配置文件(见 Create 链路) |
45
+ | 获取调用投影 | `npx @lark-apaas/miaoda-cli plugin list --id <id>`(见调用代码生成) |
46
+ | 非流式调用 | `capabilityClient.load(id).call(actionKey, input)` |
47
+ | 流式调用 | `capabilityClient.load(id).callStream(actionKey, input)` |
48
+ | capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
49
+ | 插件实例配置位置 | `server/capabilities/<instance_id>.json`(默认纯前端应用 / appType=3)。见「配置目录」段 |
50
+
51
+ > **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。错误写法:`const dataloom = await getDataloom(); (dataloom as any).capability` — 这样写不会工作。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
52
+
53
+ ## Plugin 代码编写指南
54
+
55
+ 以下场景**必须**先读取 `references/plugin-coding-guide.md` 再编写代码:
56
+
57
+ - 编写 `capabilityClient` 调用代码时(含导入路径、调用方式)
58
+ - 处理流式输出(`outputMode = stream`),包括多插件并行流式、单插件 JSON 流式解析
59
+ - 需要根据 `outputMode` 选择 `call()` 或 `callStream()` 时
60
+
61
+ ## Plugin 链式调用(Plugin Chain)
62
+
63
+ 很多业务场景需要多个插件串联完成,**禁止用正则/字符串解析替代 AI 插件做结构化提取**。
64
+
65
+ ### 插件能力分类
66
+
67
+ | 类别 | 插件 | 输入→输出 |
68
+ |------|------|----------|
69
+ | **内容提取** | `ai-doc-parser` | 文档(PDF/DOC/PPTX/XLSX/CSV等9种)→纯文本 |
70
+ | **内容提取** | `ai-speech-to-text` | 音频→纯文本 |
71
+ | **内容提取** | `ai-image-understanding` | 图片→文本描述(流式,适合理解/问答) |
72
+ | **结构化提取** | `ai-text-to-json` | 文本→结构化 JSON(最多20字段) |
73
+ | **结构化提取** | `ai-image-to-json` | 图片→结构化 JSON(最多20字段,**单步直达**) |
74
+ | **结构化提取** | `ai-categorization` | 文本→分类标签 |
75
+ | **内容生成** | `ai-text-generate` | 提示词→文本(流式) |
76
+ | **内容生成** | `ai-text-to-image` | 文字描述→图片 |
77
+ | **内容生成** | `ai-text-summary` | 长文本→摘要(流式) |
78
+ | **内容生成** | `ai-translate` | 文本→翻译(12种语言,流式) |
79
+ | **内容生成** | `ai-search-summary` | 搜索词→网页摘要(流式) |
80
+ | **内容生成** | `ai-speech-synthesis` | 文本→语音(44+音色) |
81
+ | **图片处理** | `ai-image-matting` | 图片→抠图/去背景/去水印 |
82
+ | **图片处理** | `ai-background-replace` | 主体图+背景→合成图 |
83
+ | **图片处理** | `ai-image-to-image` | 参考图(1-5张)+描述→编辑/风格转换 |
84
+ | **图片处理** | `ai-image-compare` | 两张图→对比分析(流式) |
85
+
86
+ ### 决策树:选择单步还是链式
87
+
88
+ ```
89
+ 输入是什么?
90
+ ├── 文档文件 → 必须先用 ai-doc-parser 提取文本,再根据目标选择下游插件:
91
+ │ ├── 需要结构化数据 → ai-doc-parser → ai-text-to-json (2步链)
92
+ │ ├── 需要摘要 → ai-doc-parser → ai-text-summary
93
+ │ ├── 需要翻译 → ai-doc-parser → ai-translate
94
+ │ ├── 需要分类 → ai-doc-parser → ai-categorization
95
+ │ └── 仅需原文 → ai-doc-parser(单步)
96
+ ├── 图片 → ⚠️ 注意选择正确的插件:
97
+ │ ├── 提取结构化数据(发票/名片/证件等)→ ai-image-to-json(⭐ 单步直达!)
98
+ │ ├── 理解内容后提取结构化数据 → ai-image-understanding → ai-text-to-json(2步链)
99
+ │ ├── 抠图后换背景 → ai-image-matting → ai-background-replace(2步链)
100
+ │ └── 理解/问答/编辑/对比 → 对应单插件即可
101
+ ├── 音频
102
+ │ ├── 需要结构化数据 → ai-speech-to-text → ai-text-to-json(2步链)
103
+ │ └── 仅需文字 → ai-speech-to-text(单步)
104
+ └── 纯文本
105
+ ├── 需要结构化数据 → ai-text-to-json(⭐ 单步直达!)
106
+ └── 摘要/翻译/分类/生成 → 对应单插件即可
107
+ ```
108
+
109
+ ### 常见 Plugin Chain 组合
110
+
111
+ | 链路 | 插件组合 | 场景举例 |
112
+ |------|---------|---------|
113
+ | 文档→结构化数据 | `ai-doc-parser` → `ai-text-to-json` | 简历PDF→员工档案、合同→结构化条款 |
114
+ | 文档→摘要 | `ai-doc-parser` → `ai-text-summary` | 研报PDF→摘要、长文档→概要 |
115
+ | 文档→翻译 | `ai-doc-parser` → `ai-translate` | 英文论文→中文翻译 |
116
+ | 文档→分类 | `ai-doc-parser` → `ai-categorization` | 工单文档→类型标签 |
117
+ | 图片→结构化数据 | `ai-image-to-json`(**单步**) | 发票→金额/日期、名片→联系人 |
118
+ | 图片理解→结构化 | `ai-image-understanding` → `ai-text-to-json` | 复杂图表→数据、截图→字段 |
119
+ | 音频→结构化数据 | `ai-speech-to-text` → `ai-text-to-json` | 会议录音→待办事项 |
120
+ | 音频→翻译 | `ai-speech-to-text` → `ai-translate` | 外语录音→中文 |
121
+ | 抠图→换背景 | `ai-image-matting` → `ai-background-replace` | 商品图→电商主图 |
122
+
123
+ ### Plugin Chain 调用模式
124
+
125
+ ```typescript
126
+ // 示例:文档 → 结构化数据(2步链)
127
+ // Step 1: 内容提取(注意:fileUrl 通常为数组类型,必须按 inputSchema 传入)
128
+ const rawResult = await capabilityClient
129
+ .load('doc_parser_instance')
130
+ .call('parseDocToMarkdown', { fileUrl: [docUrl] });
131
+
132
+ // Step 2: AI 结构化提取
133
+ const structured = await capabilityClient
134
+ .load('text_to_json_instance')
135
+ .call('textToJson', { text: rawResult.content });
136
+
137
+ // Step 3: 使用结果(填入表单 / 写入数据库等)
138
+ ```
139
+
140
+ > **Client 侧提示**:`capabilityClient` 支持直接传 File/Blob 对象作为文件参数,无需先上传到 dataloom 获取 URL:
141
+ > ```typescript
142
+ > // Client 侧:直接传 File 对象,SDK 自动处理上传
143
+ > const rawResult = await capabilityClient
144
+ > .load('doc_parser_instance')
145
+ > .call('parseDocToMarkdown', { fileUrl: [file] }); // file 为 File/Blob 对象
146
+ > ```
147
+ > ⚠️ `capabilityClient` 支持此能力,SDK 自动处理文件上传。
148
+
149
+ ### 创建结构化提取 PluginInstance 的关键要求
150
+
151
+ 创建 `ai-text-to-json` 或 `ai-image-to-json` 类型的 PluginInstance 时:
152
+ 1. **必须一次性定义所有需要提取的字段**(参考数据库 schema / 表单定义 / UI 设计),宁多勿漏
153
+ 2. 字段类型仅支持 String / Number / Boolean,最多 20 个字段
154
+ 3. 先获取调用投影确认上游插件的 `outputSchema`,确保输入格式正确
155
+ 4. 图片→结构化数据场景,优先使用 `ai-image-to-json`(单步),避免不必要的链式调用
156
+
157
+ ## 核心概念
158
+
159
+ - **插件包(Plugin Package)**:npm 格式的功能包,安装到 `node_modules/`,含 `manifest.json` 描述 actions 和 form.schema。
160
+ - **插件实例(Plugin Instance / Capability)**:基于插件包创建的业务配置,存储在 `server/capabilities/{id}.json`(默认纯前端应用,路径规则见「配置目录」),定义 `paramsSchema`(业务入参)和 `formValue`(表单映射,通过 `{{input.xxx}}` 引用 paramsSchema 参数)。
161
+ - **变量映射**:`调用方传值 → paramsSchema 定义变量 → formValue 消费变量 {{input.xxx}} → Plugin form.schema 接收`。
162
+
163
+ ### 插件包 ≠ npm 包(必读)
164
+
165
+ | | 插件包 | npm 依赖 |
166
+ |------|------|------|
167
+ | 写入字段 | `package.json` → **`actionPlugins`** | `package.json` → `dependencies` / `devDependencies` |
168
+ | 用途 | 妙搭平台 AI 能力 | 项目依赖库 |
169
+ | **禁止** | ❌ 不能用 `npm install` 装插件包 | ❌ 不能用 `npm install` 管理插件包 |
170
+
171
+ 两套机制完全独立。混淆会导致运行时找不到插件。
172
+
173
+ ### 插件实例配置目录
174
+
175
+ 插件实例配置文件(`<instance_id>.json`)存放在 capabilities 目录下。按以下优先级确定路径:
176
+
177
+ 1. 环境变量 `MIAODA_CAPABILITIES_DIR` → 直接使用
178
+ 2. 环境变量 `MIAODA_APP_TYPE` 或 `.env.local` 中的 `MIAODA_APP_TYPE`:
179
+ - `6` → `shared/capabilities/`(纯前端应用 appType=6)
180
+ - 其他 → `server/capabilities/`(Design 应用 appType=3)
181
+ 3. 以上都未设置 → 默认 `server/capabilities/`
182
+
183
+ 目录不存在时 `mkdir -p` 创建。
184
+
185
+ ### 插件实例配置结构
186
+
187
+ 写入 `server/capabilities/<id>.json` 的 JSON 结构:
188
+
189
+ ```json
190
+ {
191
+ "id": "<语义化ID>",
192
+ "pluginKey": "<pluginKey>",
193
+ "pluginVersion": "<version>",
194
+ "name": "<实例名称>",
195
+ "description": "<实例描述>",
196
+ "paramsSchema": { ... },
197
+ "formValue": { ... },
198
+ "createdAt": <unix毫秒>,
199
+ "updatedAt": <unix毫秒>
200
+ }
201
+ ```
202
+
203
+ **paramsSchema 支持 4 种参数类型**:
204
+
205
+ 1. **文本**:`{ "type": "string", "description": "..." }`
206
+ 2. **数组**:`{ "type": "array", "description": "...", "items": { "type": "string" } }`
207
+ 3. **图片**:`{ "type": "array", "format": "plugin-image-url", "description": "...", "items": { "type": "string" } }`
208
+ 4. **文件**:`{ "type": "array", "format": "plugin-file-url", "description": "...", "items": { "type": "string" } }`
209
+
210
+ 只允许 string 和 array 两种 type。array 必须有 items。format 只允许 `plugin-image-url` 或 `plugin-file-url`。
211
+
212
+ > **注意**:`format: "plugin-image-url"` 和 `format: "plugin-file-url"` 的字段支持直接传入 File/Blob 对象,`capabilityClient` SDK 会自动处理上传。
213
+
214
+ ---
215
+
216
+ ## CRUD 链路
217
+
218
+ ### Create 链路
219
+
220
+ 创建一个**插件实例**,即能力目录下的 `<id>.json` 配置文件。
221
+
222
+ 1. **安装插件包** — 由 lark-apps skill 引导完成
223
+ 2. **读 manifest** — `cat node_modules/<pluginKey>/manifest.json`
224
+ 3. **确定配置目录** — 按上方「插件实例配置目录」规则确定,不存在则 `mkdir -p` 创建
225
+ 4. **设计实例配置** — 确定 id / name / paramsSchema / formValue
226
+ 5. **自查规则** — 写入前逐条检查:变量引用对称、类型合法、禁止模板控制语法、array 有 items。违规修正,超过 3 次上报用户
227
+ 6. **写入配置文件** — 按上方 JSON 结构写入
228
+ 7. **获取调用投影** — `npx @lark-apaas/miaoda-cli plugin list --id <id>`
229
+ 8. **编写调用代码** — 基于投影。**禁止凭记忆猜测**
230
+
231
+ ### Update 链路
232
+
233
+ 1. `cat server/capabilities/<id>.json` → 查看当前配置
234
+ 2. 读 manifest + 设计修改方案。不修改 id / pluginKey / pluginVersion / createdAt
235
+ 3. 改后自查规则,修正后写回,更新 `updatedAt`
236
+ 4. paramsSchema 变化 → `grep -rn "load('${id}')"` 扫描代码引用并更新
237
+ 5. 重新获取调用投影:`npx @lark-apaas/miaoda-cli plugin list --id <id>`
238
+
239
+ ### Delete 链路
240
+
241
+ 1. `cat server/capabilities/<id>.json` → 确认实例存在
242
+ 2. `grep -rn "load('${id}')"` → 扫描代码引用,有则先清理
243
+ 3. `rm server/capabilities/<id>.json`
244
+
245
+ ---
246
+
247
+ ## 调用投影
248
+
249
+ ### 获取调用投影(每次编写/修改调用代码前必做)
250
+
251
+ ```bash
252
+ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
253
+ ```
254
+
255
+ 输出含 `actions[].key`、`inputSchema`、`outputSchema`、`outputMode`,是编写调用代码的唯一依据。
256
+
257
+ ### 充血 Fallback 链
258
+
259
+ `npx @lark-apaas/miaoda-cli plugin list --id <id>` → 失败则 `npx fullstack-cli capability list --id <id>` → 失败则 `node .agents/skills/plugin-guide/scripts/plugin-hydrate.js <pluginKey> <instanceId>` → 失败则手动合并 manifest + capability JSON。
260
+
261
+ ### 无 Node.js 环境
262
+
263
+ 1. 告知用户未安装 Node.js
264
+ 2. 手动合并 manifest + capability JSON
265
+ 3. 如果插件有 `{dynamic: true}`:读插件源码推导逻辑 → 基于推导编写代码 → 运行正确则成立,报错则上报用户
266
+
267
+ ### 调用代码
268
+
269
+ 根据 `actions[].outputMode` 选择调用方式:
270
+ - `unary` → `capabilityClient.load(id).call(actionKey, input)`
271
+ - `stream` → `capabilityClient.load(id).callStream(actionKey, input)`
272
+
273
+ **必读**:`references/plugin-coding-guide.md`
274
+
275
+ ### 代码放置
276
+
277
+ 调用代码放在 `client/` 目录下的组件/hooks 中,由用户交互触发。
278
+
279
+ ---
280
+
281
+
282
+ ## 业务语义映射约定
283
+
284
+ 用户会用「AI 生文」「AI 生图」「AI 摘要」等**业务语言**描述需求;这些关键词必须被识别为**待使用或待创建的 PluginInstance**。
285
+
286
+ 开发流程:
287
+ 1. 收到需求后,先 `ls server/capabilities/` 看是否有可复用的实例
288
+ 2. 若有候选但不确定是否满足,获取调用投影查看其 actions/schema/outputMode 再决策
289
+ 3. 若无,按 Create 链路新建实例
290
+ 4. 生成 `capabilityClient` 调用代码
291
+
292
+ ## 插件调用错误处理规范
293
+
294
+ | 错误类型 | 应对策略 |
295
+ |----------|---------|
296
+ | `InputValidationError` | 修复参数后重试 |
297
+ | `RateLimitError` | 指数退避重试(1s/2s/4s),最多 3 次 |
298
+ | `ExecutionError` | 记录日志 + 降级方案 + 通知用户 |
299
+ | 网络超时 | 重试 + 超时后降级 |
300
+
301
+ **规则**: (1) 禁止静默吞异常,每个 catch 必须向用户展示错误或触发补偿;(2) 异步调用必须有终态(success/failed),前端不能永远 loading;(3) 通知类插件失败必须有补偿机制。
302
+
303
+ ## 缓存与幂等性
304
+
305
+ - AI 类插件**没有请求级缓存**。同样输入返回相似结果是 LLM 低 temperature 下的正常行为。
306
+ - **禁止**通过修改业务参数注入 UUID 来"绕缓存",会污染 AI 输入导致输出包含垃圾文本。
307
+ - 需要不同结果时:调整 temperature、修改 prompt 要求、或使用不同业务上下文。
308
+
309
+ ## 插件参数来源规范
310
+
311
+ | 类型 | 来源 | 示例 |
312
+ |------|------|------|
313
+ | 业务数据 | DB 查询或前端传入 | 候选人姓名、简历内容 |
314
+ | 运行时配置 | 配置/环境变量/平台 API | 接收人 user_id、阈值 |
315
+
316
+ **禁止**硬编码运行时配置值(user_id、邮箱等),必须从配置表/平台 API/环境变量获取。
@@ -0,0 +1,126 @@
1
+ ## PluginInstance 代码编写指南
2
+
3
+ ### 核心原则
4
+
5
+ **严禁** import { capabilityClient } from '@lark-apaas/client-capability'。
6
+ **唯一指定**的导入方式是 import { capabilityClient } from '@lark-apaas/client-toolkit';
7
+
8
+ | 场景 | 调用方式 |
9
+ |------|---------|
10
+ | 非流式调用 | `capabilityClient.load(id).call()` |
11
+ | 流式输出场景 | `capabilityClient.load(id).callStream()` |
12
+
13
+ ---
14
+
15
+ ### 调用方式
16
+
17
+ #### 1. 调用前获取权威依据
18
+ 在为某个插件实例生成调用代码前,必须先通过 `get_plugin_ai_json` 工具获取该插件实例的运行时投影(plugin_Instance.ai.json),并以其中信息为准:
19
+ - `actions[].key`:调用时要传的 `actionKey`
20
+ - `actions[].inputSchema / outputSchema`:入参/出参结构
21
+ - `actions[].outputMode`:`unary | stream`(决定调用与结果处理方式)
22
+
23
+ #### call / callStream 函数签名
24
+
25
+ ```typescript
26
+ .call(actionKey: string, input: object) // 非流式,返回 Promise<output>
27
+ .callStream(actionKey: string, input: object) // 流式,返回 AsyncIterable<chunk>
28
+ ```
29
+
30
+ - **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
31
+ - **第二个参数 `input`**:必须是对象,结构符合 `actions[].inputSchema`
32
+
33
+ ```typescript
34
+ // ❌ 错误:把参数 JSON.stringify 后当作 actionKey
35
+ plugin.call(JSON.stringify({ meeting_title: '...' }));
36
+ // ❌ 错误:漏掉 actionKey,直接传参数对象
37
+ plugin.call({ meeting_title: '...' });
38
+
39
+ // ✅ 正确:第一个参数是 actionKey 字符串,第二个参数是 input 对象
40
+ plugin.call('send_feishu_message', { meeting_title: '...' });
41
+ ```
42
+
43
+ #### 2. 非流式调用(outputMode = "unary")
44
+
45
+ ```typescript
46
+ import { capabilityClient } from '@lark-apaas/client-toolkit';
47
+ import { logger } from "@lark-apaas/client-toolkit/logger";
48
+
49
+ const result = await capabilityClient
50
+ .load('create_feishu_group')
51
+ .call('createGroup', {
52
+ group_name: '项目讨论群',
53
+ members: ['user_001', 'user_002'],
54
+ });
55
+
56
+ logger.info(result);
57
+ ```
58
+
59
+ #### 3. 流式调用(outputMode = "stream")
60
+
61
+ ##### 场景判断与推荐方案
62
+
63
+ | 场景 | 特征 | 推荐度 |
64
+ |-----|------|-------|
65
+ | **多插件并行流式** | 多个插件各返回单一输出,并行调用 | **优先推荐** |
66
+ | **单插件 JSON 流式解析** | 单插件返回结构化 JSON,需边接收边解析 | ⚠️ 仅在必要时 |
67
+
68
+ **核心原则**:在插件设计阶段按「原子化拆解」拆分,避免单插件返回多字段 JSON。
69
+
70
+ ##### 推荐:多插件并行流式
71
+
72
+ 适用于需求涉及多种输出(标题、正文、图片等),各输出相对独立。
73
+
74
+ ```tsx
75
+ import { logger } from "@lark-apaas/client-toolkit/logger";
76
+
77
+ function MultiPluginStreamExample() {
78
+ const [title, setTitle] = useState('');
79
+ const [content, setContent] = useState('');
80
+ const [coverUrl, setCoverUrl] = useState('');
81
+
82
+ const handleGenerate = async (keywords: string) => {
83
+ // 1. 封面图(非流式,异步不阻塞)
84
+ capabilityClient
85
+ .load('cover_generator')
86
+ .call<{ images: string[] }>('textToImage', { keywords })
87
+ .then(res => res?.images?.[0] && setCoverUrl(res.images[0]))
88
+ .catch(err => logger.warn('封面生成失败', err));
89
+
90
+ // 2. 标题(非流式)
91
+ capabilityClient
92
+ .load('title_generator')
93
+ .call<{ content: string }>('textGenerate', { keywords })
94
+ .then(res => res?.content && setTitle(res.content));
95
+
96
+ // 3. 正文(流式,边生成边展示)
97
+ const contentStream = capabilityClient
98
+ .load('content_generator')
99
+ .callStream<{ content: string }>('textGenerate', { keywords });
100
+
101
+ // 🎯 流式内容直接拼接,无需复杂解析
102
+ for await (const chunk of contentStream) {
103
+ setContent(prev => prev + (chunk.content || ''));
104
+ }
105
+ };
106
+
107
+ return (/* 各字段独立渲染 */);
108
+ }
109
+ ```
110
+
111
+ **优点**:代码简洁、各插件独立、某个失败不影响其他。
112
+
113
+ ##### ⚠️ 兜底:单插件 JSON 流式解析
114
+
115
+ 当无法拆分为多插件时,需处理不完整 JSON 的逐字符到达,需实现 `parseStreamingStringField` 和 `extractJsonObject` 工具函数。**强烈建议在插件设计阶段拆分为多插件并行流式调用,避免此场景。**
116
+
117
+ ### outputMode 与调用方式
118
+
119
+ 先通过 `get_plugin_ai_json(pluginInstanceId)` 获取 `actions[].outputMode`:
120
+
121
+ | outputMode | 调用方式 |
122
+ |------------|---------|
123
+ | `unary` | `capabilityClient.load(id).call(actionKey, input)` |
124
+ | `stream` | `capabilityClient.load(id).callStream(actionKey, input)` |
125
+
126
+ ---