yuanflow-cli 0.1.7 → 0.1.9
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/README.md +13 -5
- package/package.json +1 -1
- package/skills/yuanflow-skill/SKILL.md +14 -1
- package/skills/yuanflow-skill//347/224/237/345/233/276/346/212/200/350/203/275/SKILL.md +84 -0
- package/skills/yuanflow-skill//350/207/252/345/252/222/344/275/223/347/237/245/350/257/206/345/272/223/SKILL.md +7 -4
- package/src/agent-protocol.js +15 -0
- package/src/atomic-request.js +39 -0
- package/src/cli.js +1 -1
- package/src/knowledge-tools.js +4 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ yuanflow-cli works download --platform douyin --target "https://v.douyin.com/xxx
|
|
|
36
36
|
yuanflow-cli search content --platform xiaohongshu --keyword "美妆" --format agent-json
|
|
37
37
|
yuanflow-cli search users --platform instagram --keyword "nasa" --format agent-json
|
|
38
38
|
yuanflow-cli knowledge docs --format agent-json
|
|
39
|
-
yuanflow-cli knowledge entry --
|
|
39
|
+
yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
|
|
40
40
|
yuanflow-cli oss signed-url --key path/to/file.png --ttl-seconds 1800 --format agent-json
|
|
41
41
|
yuanflow-cli list douyin
|
|
42
42
|
```
|
|
@@ -96,12 +96,12 @@ Agent 不确定参数时先执行 `commands list`,再用 `schema works.douyin.
|
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
yuanflow-cli knowledge docs --format agent-json
|
|
99
|
-
yuanflow-cli knowledge entry --
|
|
100
|
-
yuanflow-cli knowledge rules --pack-code topic_pain_point_pack --
|
|
101
|
-
yuanflow-cli knowledge rule-detail --rule-code some_rule_code --
|
|
99
|
+
yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
|
|
100
|
+
yuanflow-cli knowledge rules --pack-code topic_pain_point_pack --output-format short_video_script --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
|
|
101
|
+
yuanflow-cli knowledge rule-detail --rule-code some_rule_code --output-format short_video_script --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Agent 应先查看 `knowledge docs`,再把用户需求整理成 `task_frame`,按接口返回的 `next_actions` 渐进查询。
|
|
104
|
+
Agent 应先查看 `knowledge docs`,再把用户需求整理成 `task_frame`,按接口返回的 `next_actions` 渐进查询。v2 接口不要求 `task_intent`;旧字段仅保留兼容。`packs`、`rules` 的 `data` 可能直接是数组,Agent 不应假设固定对象结构,应优先读取 `next_actions` 继续下钻。
|
|
105
105
|
|
|
106
106
|
### OSS 原子能力
|
|
107
107
|
|
|
@@ -115,6 +115,14 @@ yuanflow-cli oss copy --source-key temp/cover.png --target-key final/cover.png -
|
|
|
115
115
|
|
|
116
116
|
上传本地文件前必须确认用户授权,不能上传密钥、cookie、账号凭据或隐私文件。
|
|
117
117
|
|
|
118
|
+
### 生图技能
|
|
119
|
+
|
|
120
|
+
`生图技能` 随 npm 包安装到 Skill bundle。YuanFlow 主程序内优先使用受控工具 `yuanflow_image_request`,自动注入当前用户 token 并缓存返回图片。
|
|
121
|
+
|
|
122
|
+
- 生成图片:`/v1/images/generations`,主模型 `gpt-image-2-c`,备用模型 `gpt-image-2`,请求体使用 `prompt`。
|
|
123
|
+
- 编辑图片:`/v1/images/edits`,必须使用 multipart/form-data 上传本地图片文件。
|
|
124
|
+
- `gpt-image-2-c` 通常返回 URL,`gpt-image-2` 通常返回 `b64_json`;两种返回都应缓存后再展示。
|
|
125
|
+
|
|
118
126
|
## Skill 安装器
|
|
119
127
|
|
|
120
128
|
```bash
|
package/package.json
CHANGED
|
@@ -20,12 +20,13 @@ description: Use when the user asks about social-media API workflows, platform d
|
|
|
20
20
|
- `综合用户搜索工具/`
|
|
21
21
|
- `自媒体知识库/`
|
|
22
22
|
- `OSS文件中转工具/`
|
|
23
|
+
- `生图技能/`
|
|
23
24
|
|
|
24
25
|
## 环境判断
|
|
25
26
|
|
|
26
27
|
执行本 Skill 前,先判断当前运行环境:
|
|
27
28
|
|
|
28
|
-
1. 如果当前 Agent 可用 YuanFlow 内置工具,例如 `yuanflow_cli_call` 或 `
|
|
29
|
+
1. 如果当前 Agent 可用 YuanFlow 内置工具,例如 `yuanflow_cli_call`、`yuanflow_gateway_request` 或 `yuanflow_image_request`,优先使用 YuanFlow 内置工具。不要要求用户安装 npm,也不要要求用户配置 token。
|
|
29
30
|
2. 如果当前不在 YuanFlow 内,且本地可执行 `yuanflow-cli --help`,使用本地 CLI。
|
|
30
31
|
3. 如果当前不在 YuanFlow 内,且本地没有 `yuanflow-cli`,再提示用户安装 `npm install -g yuanflow-cli`。
|
|
31
32
|
|
|
@@ -139,6 +140,18 @@ description: Use when the user asks about social-media API workflows, platform d
|
|
|
139
140
|
|
|
140
141
|
- `OSS文件中转工具`
|
|
141
142
|
|
|
143
|
+
### 9. 走 `生图技能`
|
|
144
|
+
|
|
145
|
+
遇到下面这些需求,优先进入这个子 Skill:
|
|
146
|
+
|
|
147
|
+
- 生成图片、画图、出图、做海报、生成视觉素材。
|
|
148
|
+
- 编辑图片、修改本地图片、基于已有图片做变化。
|
|
149
|
+
- 调用 YuanFlow 内置图片生成或图片编辑接口。
|
|
150
|
+
|
|
151
|
+
子 Skill 名称:
|
|
152
|
+
|
|
153
|
+
- `生图技能`
|
|
154
|
+
|
|
142
155
|
## 多需求时怎么处理
|
|
143
156
|
|
|
144
157
|
如果用户一次提了多段流程,不要强行塞进一个子 Skill,按阶段拆开:
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 生图技能
|
|
3
|
+
description: Use when the user asks to generate images, create pictures, make posters or visual assets, edit images, modify local pictures, or call YuanFlow image generation/edit APIs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 生图技能
|
|
7
|
+
|
|
8
|
+
当用户提到生成图片、画图、出图、做海报、生成视觉素材、编辑图片或修改本地图片时,优先使用本 Skill。
|
|
9
|
+
|
|
10
|
+
## YuanFlow 内置环境
|
|
11
|
+
|
|
12
|
+
如果当前 Agent 可用 `yuanflow_image_request`,优先使用该受控工具。它会自动注入当前用户 token,并把接口返回的 URL 或 base64 图片缓存为可预览资源。
|
|
13
|
+
|
|
14
|
+
生成图片:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mode": "generate",
|
|
19
|
+
"model": "gpt-image-2-c",
|
|
20
|
+
"prompt": "一张写实产品图:白色马克杯放在木桌上,自然光",
|
|
21
|
+
"size": "1024x1024"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
编辑图片:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mode": "edit",
|
|
30
|
+
"model": "gpt-image-2-c",
|
|
31
|
+
"prompt": "把杯子改成红色,保持背景不变",
|
|
32
|
+
"image_path": "D:\\path\\to\\source.png"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 接口顺序
|
|
37
|
+
|
|
38
|
+
生成图片只走 `/v1/images/generations`,请求体使用 `prompt`:
|
|
39
|
+
|
|
40
|
+
1. 主模型:`gpt-image-2-c`
|
|
41
|
+
2. 备用模型:`gpt-image-2`
|
|
42
|
+
|
|
43
|
+
只有主模型请求失败或返回失败时,才切换到备用模型。不要使用 `gpt-image`、`doubao-生图` 或 chat completions/responses 端点作为本 Skill 的生图路径。
|
|
44
|
+
|
|
45
|
+
编辑图片只走 `/v1/images/edits`。编辑模式必须使用 `multipart/form-data` 上传本地图片文件,不能用 JSON 直接传图片路径。
|
|
46
|
+
|
|
47
|
+
已确认返回差异:
|
|
48
|
+
|
|
49
|
+
- `gpt-image-2-c` 通常返回图片 URL。
|
|
50
|
+
- `gpt-image-2` 通常返回 `b64_json` 图片数据。
|
|
51
|
+
- 两种返回都需要缓存成本地资源,并在最终回复中给出可显示的图片链接。
|
|
52
|
+
|
|
53
|
+
## 外部 Agent 或手工调用
|
|
54
|
+
|
|
55
|
+
生成图片请求示例:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
curl https://open.yuanchuangai.com/v1/images/generations \
|
|
59
|
+
-H "Authorization: Bearer $YUAN_API_KEY" \
|
|
60
|
+
-H "Content-Type: application/json" \
|
|
61
|
+
-d '{
|
|
62
|
+
"model": "gpt-image-2-c",
|
|
63
|
+
"prompt": "一张写实产品图:白色马克杯放在木桌上,自然光"
|
|
64
|
+
}'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
如果主模型失败,把 `model` 改为 `gpt-image-2` 后重试。
|
|
68
|
+
|
|
69
|
+
编辑图片请求示例:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
curl https://open.yuanchuangai.com/v1/images/edits \
|
|
73
|
+
-H "Authorization: Bearer $YUAN_API_KEY" \
|
|
74
|
+
-F "model=gpt-image-2-c" \
|
|
75
|
+
-F "prompt=把杯子改成红色,保持背景不变" \
|
|
76
|
+
-F "image=@D:/path/to/source.png"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 输出要求
|
|
80
|
+
|
|
81
|
+
- 返回图片 URL 时,先缓存图片,再回复缓存后的可预览 URL。
|
|
82
|
+
- 返回 `b64_json` 时,先解码保存为图片文件,再回复缓存后的可预览 URL。
|
|
83
|
+
- 用户未指定保存目录时,使用 YuanFlow 程序资源缓存目录。
|
|
84
|
+
- 回复中不要暴露 token、Authorization header 或完整鉴权配置。
|
|
@@ -50,10 +50,10 @@ yuanflow-cli knowledge docs --format agent-json
|
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
yuanflow-cli knowledge entry \
|
|
53
|
-
--task-intent generate_script \
|
|
54
53
|
--output-format short_video_script \
|
|
55
54
|
--domain 自媒体运营 \
|
|
56
55
|
--content-goal "写一个面向创业者的短视频选题" \
|
|
56
|
+
--target-audience 创业者 \
|
|
57
57
|
--tone 清晰直接 \
|
|
58
58
|
--communication-mode 口播 \
|
|
59
59
|
--format agent-json
|
|
@@ -64,8 +64,9 @@ yuanflow-cli knowledge entry \
|
|
|
64
64
|
```bash
|
|
65
65
|
yuanflow-cli knowledge rules \
|
|
66
66
|
--pack-code topic_pain_point_pack \
|
|
67
|
-
--
|
|
67
|
+
--output-format short_video_script \
|
|
68
68
|
--content-goal "写一个面向创业者的短视频选题" \
|
|
69
|
+
--target-audience 创业者 \
|
|
69
70
|
--format agent-json
|
|
70
71
|
```
|
|
71
72
|
|
|
@@ -80,14 +81,14 @@ yuanflow-cli knowledge rules \
|
|
|
80
81
|
"args": [
|
|
81
82
|
"knowledge",
|
|
82
83
|
"entry",
|
|
83
|
-
"--task-intent",
|
|
84
|
-
"generate_script",
|
|
85
84
|
"--output-format",
|
|
86
85
|
"short_video_script",
|
|
87
86
|
"--domain",
|
|
88
87
|
"自媒体运营",
|
|
89
88
|
"--content-goal",
|
|
90
89
|
"写一个面向创业者的短视频选题",
|
|
90
|
+
"--target-audience",
|
|
91
|
+
"创业者",
|
|
91
92
|
"--format",
|
|
92
93
|
"agent-json"
|
|
93
94
|
]
|
|
@@ -100,4 +101,6 @@ yuanflow-cli knowledge rules \
|
|
|
100
101
|
- 不要使用 `/atomic/aliyun-db/query` 查询知识库内部表。
|
|
101
102
|
- 不要把知识库核心方法论静态写入回复,只按接口返回内容使用。
|
|
102
103
|
- 如果接口返回 `next_actions`,优先按它推荐的动作继续查询。
|
|
104
|
+
- v2 接口不要求 `task_intent`;旧字段只作为兼容字段,不要在新调用里默认填写。
|
|
105
|
+
- `packs`、`rules` 的 `data` 可能直接是数组,不要假设固定 `{ packs: [] }` 或 `{ rules: [] }` 结构。
|
|
103
106
|
- 如果用户只是在问“有什么知识库方向”,先用 `knowledge docs`。
|
package/src/agent-protocol.js
CHANGED
|
@@ -29,12 +29,27 @@ const ERROR_MAP = [
|
|
|
29
29
|
retryable: false,
|
|
30
30
|
test: (message) => message.includes('HTTP 401') || message.includes('HTTP 403'),
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
code: 'UPSTREAM_CONFIG_ERROR',
|
|
34
|
+
exitCode: 4,
|
|
35
|
+
retryable: false,
|
|
36
|
+
test: (message) => message.includes('原子能力主站令牌未配置'),
|
|
37
|
+
},
|
|
32
38
|
{
|
|
33
39
|
code: 'UPSTREAM_ERROR',
|
|
34
40
|
exitCode: 4,
|
|
35
41
|
retryable: true,
|
|
36
42
|
test: (message) => /HTTP 5\d\d/.test(message),
|
|
37
43
|
},
|
|
44
|
+
{
|
|
45
|
+
code: 'UPSTREAM_ROUTE_MISSING',
|
|
46
|
+
exitCode: 4,
|
|
47
|
+
retryable: false,
|
|
48
|
+
test: (message) =>
|
|
49
|
+
message.includes('Invalid URL') ||
|
|
50
|
+
message.includes('接口返回了前端 HTML 页面') ||
|
|
51
|
+
message.includes('接口返回了非 JSON 内容'),
|
|
52
|
+
},
|
|
38
53
|
{
|
|
39
54
|
code: 'NETWORK_ERROR',
|
|
40
55
|
exitCode: 5,
|
package/src/atomic-request.js
CHANGED
|
@@ -45,11 +45,22 @@ export async function callAtomic(path, options = {}) {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
const text = await response.text();
|
|
48
|
+
const contentType = response.headers.get('content-type') || '';
|
|
48
49
|
const payload = parseMaybeJson(text);
|
|
49
50
|
if (!response.ok) {
|
|
50
51
|
const message = typeof payload === 'object' ? JSON.stringify(payload) : text;
|
|
51
52
|
throw new Error(`请求失败:HTTP ${response.status} ${message}`);
|
|
52
53
|
}
|
|
54
|
+
if (options.expectJson !== false) {
|
|
55
|
+
assertJsonResponse({
|
|
56
|
+
contentType,
|
|
57
|
+
method,
|
|
58
|
+
path: normalizedPath,
|
|
59
|
+
status: response.status,
|
|
60
|
+
payload,
|
|
61
|
+
text,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
53
64
|
return payload;
|
|
54
65
|
}
|
|
55
66
|
|
|
@@ -95,6 +106,34 @@ function parseMaybeJson(text) {
|
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
function assertJsonResponse({ contentType, method, path, status, payload, text }) {
|
|
110
|
+
if (payload !== null && typeof payload === 'object') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const preview = compactPreview(text);
|
|
114
|
+
const isHtml = looksLikeHtml(text) || contentType.toLowerCase().includes('text/html');
|
|
115
|
+
if (isHtml) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`接口返回了前端 HTML 页面,不是结构化 JSON。请检查云端接口是否已部署或路径是否正确:HTTP ${status} ${method} ${path}。返回预览:${preview}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
`接口返回了非 JSON 内容。请检查云端接口是否已部署或路径是否正确:HTTP ${status} ${method} ${path}。返回预览:${preview}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function looksLikeHtml(text) {
|
|
126
|
+
const normalized = String(text || '').trim().toLowerCase();
|
|
127
|
+
return normalized.startsWith('<!doctype') || normalized.startsWith('<html') || normalized.includes('<html');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function compactPreview(text) {
|
|
131
|
+
return String(text || '')
|
|
132
|
+
.replace(/\s+/g, ' ')
|
|
133
|
+
.trim()
|
|
134
|
+
.slice(0, 160);
|
|
135
|
+
}
|
|
136
|
+
|
|
98
137
|
function maskToken(token) {
|
|
99
138
|
if (!token) {
|
|
100
139
|
return '';
|
package/src/cli.js
CHANGED
|
@@ -539,7 +539,7 @@ function printHelp() {
|
|
|
539
539
|
yuanflow-cli search content --platform xiaohongshu --keyword "美妆" --dry-run
|
|
540
540
|
yuanflow-cli search users --platform instagram --keyword "nasa" --dry-run
|
|
541
541
|
yuanflow-cli knowledge docs --dry-run
|
|
542
|
-
yuanflow-cli knowledge entry --
|
|
542
|
+
yuanflow-cli knowledge entry --output-format short_video_script --domain 自媒体运营 --content-goal "写一个创业者短视频选题" --target-audience 创业者 --format agent-json
|
|
543
543
|
yuanflow-cli oss signed-url --key path/to/file.png --ttl-seconds 1800 --format agent-json
|
|
544
544
|
yuanflow-cli list douyin
|
|
545
545
|
|
package/src/knowledge-tools.js
CHANGED
|
@@ -92,10 +92,11 @@ function knowledgeCommand(action, description, extraOptions = []) {
|
|
|
92
92
|
|
|
93
93
|
function taskFrameOptions() {
|
|
94
94
|
return [
|
|
95
|
-
{ flag: '--task-intent', name: 'taskIntent', required: false, label: '
|
|
95
|
+
{ flag: '--task-intent', name: 'taskIntent', required: false, label: '兼容旧字段;v2 接口不要求填写。' },
|
|
96
96
|
{ flag: '--output-format', name: 'outputFormat', required: false, label: '输出格式,例如 short_video_script、social_post、any。' },
|
|
97
97
|
{ flag: '--domain', name: 'domain', required: false, label: '内容领域。' },
|
|
98
98
|
{ flag: '--content-goal', name: 'contentGoal', required: false, label: '具体创作或评估目标。' },
|
|
99
|
+
{ flag: '--target-audience', name: 'targetAudience', required: false, label: '目标受众,例如 创业者、宝妈、普通用户。' },
|
|
99
100
|
{ flag: '--tone', name: 'tone', required: false, label: '语气风格。' },
|
|
100
101
|
{ flag: '--communication-mode', name: 'communicationMode', required: false, label: '表达方式,例如 口播、图文、文章。' },
|
|
101
102
|
{ flag: '--task-frame-file', name: 'taskFrameFile', required: false, label: '读取 task_frame JSON 文件。' },
|
|
@@ -116,10 +117,11 @@ async function buildTaskFrame(options) {
|
|
|
116
117
|
: {};
|
|
117
118
|
return removeUndefined({
|
|
118
119
|
...fromFile,
|
|
119
|
-
task_intent: pick(options, 'task-intent', fromFile.task_intent
|
|
120
|
+
task_intent: pick(options, 'task-intent', fromFile.task_intent),
|
|
120
121
|
output_format: pick(options, 'output-format', fromFile.output_format || 'any'),
|
|
121
122
|
domain: pick(options, 'domain', fromFile.domain || '自媒体运营'),
|
|
122
123
|
content_goal: pick(options, 'content-goal', fromFile.content_goal || ''),
|
|
124
|
+
target_audience: pick(options, 'target-audience', fromFile.target_audience || ''),
|
|
123
125
|
tone: pick(options, 'tone', fromFile.tone || ''),
|
|
124
126
|
communication_mode: pick(options, 'communication-mode', fromFile.communication_mode || ''),
|
|
125
127
|
});
|