@aigne/doc-smith 0.0.1
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 +87 -0
- package/agents/batch-docs-detail-generator.yaml +14 -0
- package/agents/batch-translate.yaml +44 -0
- package/agents/check-detail-generated.mjs +128 -0
- package/agents/check-detail-result.mjs +141 -0
- package/agents/check-structure-planning-result.yaml +30 -0
- package/agents/check-structure-planning.mjs +54 -0
- package/agents/content-detail-generator.yaml +50 -0
- package/agents/detail-generator-and-translate.yaml +88 -0
- package/agents/detail-regenerator.yaml +107 -0
- package/agents/docs-generator.yaml +93 -0
- package/agents/format-structure-plan.mjs +23 -0
- package/agents/input-generator.mjs +142 -0
- package/agents/load-sources.mjs +329 -0
- package/agents/publish-docs.mjs +212 -0
- package/agents/reflective-structure-planner.yaml +10 -0
- package/agents/save-docs.mjs +153 -0
- package/agents/save-output.mjs +25 -0
- package/agents/save-single-doc.mjs +18 -0
- package/agents/schema/structure-plan-result.yaml +32 -0
- package/agents/schema/structure-plan.yaml +26 -0
- package/agents/structure-planning.yaml +49 -0
- package/agents/transform-detail-datasources.mjs +14 -0
- package/agents/translate.yaml +28 -0
- package/aigne.yaml +28 -0
- package/biome.json +51 -0
- package/docs-mcp/aigne.yaml +8 -0
- package/docs-mcp/get-docs-detail.mjs +42 -0
- package/docs-mcp/get-docs-structure.mjs +11 -0
- package/package.json +33 -0
- package/prompts/check-structure-planning-result.md +82 -0
- package/prompts/content-detail-generator.md +99 -0
- package/prompts/document/detail-example.md +441 -0
- package/prompts/document/detail-generator.md +95 -0
- package/prompts/document/structure-example.md +98 -0
- package/prompts/document/structure-getting-started.md +10 -0
- package/prompts/document/structure-planning.md +17 -0
- package/prompts/structure-planning.md +108 -0
- package/prompts/translator.md +69 -0
- package/tests/README.md +93 -0
- package/tests/check-detail-result.test.mjs +103 -0
- package/tests/load-sources.test.mjs +642 -0
- package/tests/test-save-docs.mjs +132 -0
- package/utils/utils.mjs +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# AIGNE DocSmith
|
|
2
|
+
|
|
3
|
+
AIGNE DocSmith is a powerful, AI-driven documentation generation tool built on the AIGNE Framework. It automates the creation of detailed, structured, and multi-language documentation directly from your source code.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automated Structure Planning:** Intelligently analyzes your codebase to generate a comprehensive and logical document structure.
|
|
8
|
+
- **AI-Powered Content Generation:** Populates the document structure with detailed, high-quality content.
|
|
9
|
+
- **Multi-Language Support:** Seamlessly translates your documentation into multiple languages.
|
|
10
|
+
- **Smart File Management:** Automatically cleans up invalid .md files that are no longer in the structure plan, preventing file accumulation.
|
|
11
|
+
- **Customizable Prompts:** Allows for fine-tuning the output by customizing the prompts used by the AI agents.
|
|
12
|
+
- **Extensible Workflow:** Built with a modular agent-based architecture, making it easy to extend and customize the documentation generation process.
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
AIGNE DocSmith utilizes a chain of AI agents to generate documentation in a three-step process:
|
|
17
|
+
|
|
18
|
+
1. **Structure Planning (`structure-planning`):** This agent analyzes the source code and creates a structured plan for the documentation. The plan is defined in `agents/structure-planning.yaml` and uses the prompt in `prompts/structure-planning.md`.
|
|
19
|
+
2. **Content Detail Generation (`content-detail-generator`):** This agent takes the structured plan and generates detailed content for each section of the documentation. This process is defined in `agents/content-detail-generator.yaml` and guided by the prompt in `prompts/document/detail-generator.md`.
|
|
20
|
+
3. **Translation (`translate`):** This agent translates the generated documentation into different languages. The translation agent is defined in `agents/translate.yaml` and uses the prompt in `prompts/translator.md`.
|
|
21
|
+
|
|
22
|
+
The main workflow is orchestrated by the `docs-generator` agent, as defined in `agents/docs-generator.yaml`.
|
|
23
|
+
|
|
24
|
+
## Getting Started
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
|
|
28
|
+
- Node.js and pnpm
|
|
29
|
+
- AIGNE Framework
|
|
30
|
+
|
|
31
|
+
### Installation
|
|
32
|
+
|
|
33
|
+
1. Clone the repository:
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/your-username/aigne-doc-smith.git
|
|
36
|
+
```
|
|
37
|
+
2. Install the dependencies:
|
|
38
|
+
```bash
|
|
39
|
+
pnpm install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
|
|
44
|
+
To generate documentation, run the following command:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
aigne run docs-generator --datasources.source-code-path=/path/to/your/code
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This will initiate the documentation generation process, and the output will be saved in the `output` directory.
|
|
51
|
+
|
|
52
|
+
## Contributing
|
|
53
|
+
|
|
54
|
+
Contributions are welcome! Please feel free to submit a pull request or open an issue if you have any suggestions or find any bugs.
|
|
55
|
+
|
|
56
|
+
## Test Commands
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
|
|
60
|
+
# 初始化
|
|
61
|
+
npx --no doc-smith run --entry-agent init
|
|
62
|
+
|
|
63
|
+
# 生成命令
|
|
64
|
+
npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent generator --model gemini:gemini-2.5-pro
|
|
65
|
+
|
|
66
|
+
# 重新生成单篇
|
|
67
|
+
npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent regenerator --input-currentPath /overview --input-feedback "输出只能使用 markdown 格式,不能使用 html 标签" --model gemini:gemini-2.5-pro
|
|
68
|
+
|
|
69
|
+
# 结构规划优化
|
|
70
|
+
npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent generator --input-structurePlanFeedback "删除 Contributing 下的子节点,只保留主节点,相关信息在主节点中显示" --model gemini:gemini-2.5-pro
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# 发布文档
|
|
74
|
+
npx --no doc-smith run --input @./doc-smith/input.yaml --format yaml --entry-agent publish
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
运行测试来验证功能:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 测试 saveDocs 方法的文件清理功能
|
|
84
|
+
node tests/test-save-docs.mjs
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
更多测试信息请查看 [tests/README.md](tests/README.md)。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type: team
|
|
2
|
+
name: batch-docs-detail-generator
|
|
3
|
+
description: 批量生成文档详情
|
|
4
|
+
skills:
|
|
5
|
+
- ./check-detail-generated.mjs
|
|
6
|
+
input_schema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
datasources:
|
|
10
|
+
type: string
|
|
11
|
+
description: 结构规划的上下文,用于辅助结构规划
|
|
12
|
+
structurePlanResult: ./schema/structure-plan-result.yaml
|
|
13
|
+
iterate_on: structurePlanResult
|
|
14
|
+
mode: sequential
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type: team
|
|
2
|
+
name: batch-translate
|
|
3
|
+
description: 批量翻译文档到多个语言
|
|
4
|
+
skills:
|
|
5
|
+
- type: team
|
|
6
|
+
skills:
|
|
7
|
+
- ./translate.yaml
|
|
8
|
+
- type: transform
|
|
9
|
+
jsonata: |
|
|
10
|
+
$merge([
|
|
11
|
+
$,
|
|
12
|
+
{ "reviewContent": translation }
|
|
13
|
+
])
|
|
14
|
+
reflection:
|
|
15
|
+
reviewer: ./check-detail-result.mjs
|
|
16
|
+
is_approved: isApproved
|
|
17
|
+
max_iterations: 3
|
|
18
|
+
return_last_on_max_iterations: true
|
|
19
|
+
input_schema:
|
|
20
|
+
type: object
|
|
21
|
+
properties:
|
|
22
|
+
translates:
|
|
23
|
+
type: array
|
|
24
|
+
items:
|
|
25
|
+
type: object
|
|
26
|
+
properties:
|
|
27
|
+
language:
|
|
28
|
+
type: string
|
|
29
|
+
content:
|
|
30
|
+
type: string
|
|
31
|
+
output_schema:
|
|
32
|
+
type: object
|
|
33
|
+
properties:
|
|
34
|
+
translates:
|
|
35
|
+
type: array
|
|
36
|
+
items:
|
|
37
|
+
type: object
|
|
38
|
+
properties:
|
|
39
|
+
language:
|
|
40
|
+
type: string
|
|
41
|
+
translation:
|
|
42
|
+
type: string
|
|
43
|
+
iterate_on: translates
|
|
44
|
+
mode: sequential
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { AnthropicChatModel } from "@aigne/anthropic";
|
|
5
|
+
import { AIGNE, TeamAgent } from "@aigne/core";
|
|
6
|
+
import { GeminiChatModel } from "@aigne/gemini";
|
|
7
|
+
import { OpenAIChatModel } from "@aigne/openai";
|
|
8
|
+
import checkDetailResult from "./check-detail-result.mjs";
|
|
9
|
+
|
|
10
|
+
// Get current script directory
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
export default async function checkDetailGenerated(
|
|
14
|
+
{ path, docsDir, sourceIds, originalStructurePlan, structurePlan, ...rest },
|
|
15
|
+
options
|
|
16
|
+
) {
|
|
17
|
+
// Check if the detail file already exists
|
|
18
|
+
const flatName = path.replace(/^\//, "").replace(/\//g, "-");
|
|
19
|
+
const fileFullName = `${flatName}.md`;
|
|
20
|
+
const filePath = join(docsDir, fileFullName);
|
|
21
|
+
let detailGenerated = true;
|
|
22
|
+
let fileContent = null;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await access(filePath);
|
|
26
|
+
// If file exists, read its content for validation
|
|
27
|
+
fileContent = await readFile(filePath, "utf8");
|
|
28
|
+
} catch {
|
|
29
|
+
detailGenerated = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if sourceIds have changed by comparing with original structure plan
|
|
33
|
+
let sourceIdsChanged = false;
|
|
34
|
+
if (originalStructurePlan && sourceIds) {
|
|
35
|
+
// Find the original node in the structure plan
|
|
36
|
+
const originalNode = originalStructurePlan.find(
|
|
37
|
+
(node) => node.path === path
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (originalNode && originalNode.sourceIds) {
|
|
41
|
+
const originalSourceIds = originalNode.sourceIds;
|
|
42
|
+
const currentSourceIds = sourceIds;
|
|
43
|
+
|
|
44
|
+
// Compare arrays (order doesn't matter, but content does)
|
|
45
|
+
if (originalSourceIds.length !== currentSourceIds.length) {
|
|
46
|
+
sourceIdsChanged = true;
|
|
47
|
+
} else {
|
|
48
|
+
// Check if any sourceId is different
|
|
49
|
+
const originalSet = new Set(originalSourceIds);
|
|
50
|
+
const currentSet = new Set(currentSourceIds);
|
|
51
|
+
|
|
52
|
+
if (originalSet.size !== currentSet.size) {
|
|
53
|
+
sourceIdsChanged = true;
|
|
54
|
+
} else {
|
|
55
|
+
// Check if any element is different
|
|
56
|
+
for (const sourceId of originalSourceIds) {
|
|
57
|
+
if (!currentSet.has(sourceId)) {
|
|
58
|
+
sourceIdsChanged = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If file exists, check content validation
|
|
68
|
+
let contentValidationFailed = false;
|
|
69
|
+
if (detailGenerated && fileContent && structurePlan) {
|
|
70
|
+
const validationResult = await checkDetailResult({
|
|
71
|
+
structurePlan,
|
|
72
|
+
reviewContent: fileContent,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!validationResult.isApproved) {
|
|
76
|
+
contentValidationFailed = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If file exists, sourceIds haven't changed, and content validation passes, no need to regenerate
|
|
81
|
+
if (detailGenerated && !sourceIdsChanged && !contentValidationFailed) {
|
|
82
|
+
return {
|
|
83
|
+
path,
|
|
84
|
+
docsDir,
|
|
85
|
+
...rest,
|
|
86
|
+
detailGenerated: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If sourceIds have changed, regenerate even if file exists
|
|
91
|
+
const aigne = await AIGNE.load(join(__dirname, "../"), {
|
|
92
|
+
models: [
|
|
93
|
+
{
|
|
94
|
+
name: OpenAIChatModel.name,
|
|
95
|
+
create: (params) => new OpenAIChatModel({ ...params }),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: AnthropicChatModel.name,
|
|
99
|
+
create: (params) => new AnthropicChatModel({ ...params }),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: GeminiChatModel.name,
|
|
103
|
+
create: (params) => new GeminiChatModel({ ...params }),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const teamAgent = TeamAgent.from({
|
|
109
|
+
name: "generate-detail",
|
|
110
|
+
skills: [aigne.agents["detail-generator-and-translate"]],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = await options.context.invoke(teamAgent, {
|
|
114
|
+
...rest,
|
|
115
|
+
docsDir,
|
|
116
|
+
path,
|
|
117
|
+
sourceIds,
|
|
118
|
+
originalStructurePlan,
|
|
119
|
+
structurePlan,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
path,
|
|
124
|
+
docsDir,
|
|
125
|
+
...rest,
|
|
126
|
+
result,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export default async function checkDetailResult({
|
|
2
|
+
structurePlan,
|
|
3
|
+
reviewContent,
|
|
4
|
+
}) {
|
|
5
|
+
const linkRegex = /(?<!\!)\[([^\]]+)\]\(([^)]+)\)/g;
|
|
6
|
+
const tableSeparatorRegex = /^\s*\|\s*-+\s*\|\s*$/;
|
|
7
|
+
const codeBlockRegex = /^\s+```(?:\w+)?$/;
|
|
8
|
+
|
|
9
|
+
let isApproved = true;
|
|
10
|
+
const detailFeedback = [];
|
|
11
|
+
|
|
12
|
+
// Create a set of allowed links, including both original paths and processed .md paths
|
|
13
|
+
const allowedLinks = new Set();
|
|
14
|
+
structurePlan.forEach((item) => {
|
|
15
|
+
// Add original path
|
|
16
|
+
allowedLinks.add(item.path);
|
|
17
|
+
|
|
18
|
+
// Add processed .md path (same logic as processContent in utils.mjs)
|
|
19
|
+
let processedPath = item.path;
|
|
20
|
+
if (processedPath.startsWith(".")) {
|
|
21
|
+
processedPath = processedPath.replace(/^\./, "");
|
|
22
|
+
}
|
|
23
|
+
let flatPath = processedPath.replace(/^\//, "").replace(/\//g, "-");
|
|
24
|
+
flatPath = `./${flatPath}.md`;
|
|
25
|
+
allowedLinks.add(flatPath);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const checkLinks = (text, source) => {
|
|
29
|
+
let match;
|
|
30
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
31
|
+
const link = match[2];
|
|
32
|
+
const trimLink = link.trim();
|
|
33
|
+
|
|
34
|
+
// Only check links that processContent would process
|
|
35
|
+
// Exclude external links and mailto
|
|
36
|
+
if (/^(https?:\/\/|mailto:)/.test(trimLink)) continue;
|
|
37
|
+
|
|
38
|
+
// Preserve anchors
|
|
39
|
+
const [path, hash] = trimLink.split("#");
|
|
40
|
+
|
|
41
|
+
// Only process relative paths or paths starting with /
|
|
42
|
+
if (!path) continue;
|
|
43
|
+
|
|
44
|
+
// Check if this link is in the allowed links set
|
|
45
|
+
if (!allowedLinks.has(trimLink)) {
|
|
46
|
+
isApproved = false;
|
|
47
|
+
detailFeedback.push(
|
|
48
|
+
`Found a dead link in ${source}: [${match[1]}](${trimLink}), ensure the link exists in the structure plan path`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const checkTableSeparators = (text, source) => {
|
|
55
|
+
// Split text into lines and check each line
|
|
56
|
+
const lines = text.split("\n");
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
if (tableSeparatorRegex.test(line)) {
|
|
60
|
+
isApproved = false;
|
|
61
|
+
detailFeedback.push(
|
|
62
|
+
`Found an incorrect table separator in ${source} at line ${
|
|
63
|
+
i + 1
|
|
64
|
+
}: ${line.trim()}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const checkSingleLine = (text, source) => {
|
|
71
|
+
// Count newline characters to check if content is only on one line
|
|
72
|
+
const newlineCount = (text.match(/\n/g) || []).length;
|
|
73
|
+
if (newlineCount === 0 && text.trim().length > 0) {
|
|
74
|
+
isApproved = false;
|
|
75
|
+
detailFeedback.push(
|
|
76
|
+
`Found single line content in ${source}: content appears to be on only one line, check for missing line breaks`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const checkCodeBlockIndentation = (text, source) => {
|
|
82
|
+
// Split text into lines and check each line
|
|
83
|
+
const lines = text.split("\n");
|
|
84
|
+
let inCodeBlock = false;
|
|
85
|
+
let codeBlockIndentLevel = 0;
|
|
86
|
+
let codeBlockStartLine = 0;
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
const line = lines[i];
|
|
90
|
+
|
|
91
|
+
// Check if this line is a code block marker
|
|
92
|
+
if (codeBlockRegex.test(line)) {
|
|
93
|
+
if (!inCodeBlock) {
|
|
94
|
+
// Starting a new code block
|
|
95
|
+
inCodeBlock = true;
|
|
96
|
+
codeBlockStartLine = i + 1;
|
|
97
|
+
// Calculate indentation level of the code block marker
|
|
98
|
+
const match = line.match(/^(\s*)(```)/);
|
|
99
|
+
codeBlockIndentLevel = match ? match[1].length : 0;
|
|
100
|
+
} else {
|
|
101
|
+
// Ending the code block
|
|
102
|
+
inCodeBlock = false;
|
|
103
|
+
codeBlockIndentLevel = 0;
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If we're inside a code block, check if content has proper indentation
|
|
109
|
+
if (inCodeBlock) {
|
|
110
|
+
const contentIndentLevel = line.match(/^(\s*)/)[1].length;
|
|
111
|
+
|
|
112
|
+
// If code block marker has indentation, content should have at least the same indentation
|
|
113
|
+
if (
|
|
114
|
+
codeBlockIndentLevel > 0 &&
|
|
115
|
+
contentIndentLevel < codeBlockIndentLevel
|
|
116
|
+
) {
|
|
117
|
+
isApproved = false;
|
|
118
|
+
detailFeedback.push(
|
|
119
|
+
`Found code block with inconsistent indentation in ${source} at line ${codeBlockStartLine}: code block marker has ${codeBlockIndentLevel} spaces indentation but content at line ${
|
|
120
|
+
i + 1
|
|
121
|
+
} has only ${contentIndentLevel} spaces indentation`
|
|
122
|
+
);
|
|
123
|
+
// Reset to avoid multiple errors for the same code block
|
|
124
|
+
inCodeBlock = false;
|
|
125
|
+
codeBlockIndentLevel = 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Check content
|
|
132
|
+
checkLinks(reviewContent, "result");
|
|
133
|
+
checkTableSeparators(reviewContent, "result");
|
|
134
|
+
checkSingleLine(reviewContent, "result");
|
|
135
|
+
checkCodeBlockIndentation(reviewContent, "result");
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
isApproved,
|
|
139
|
+
detailFeedback: detailFeedback.join("\n"),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: check-structure-planning-result
|
|
2
|
+
description: 对 structure-planning agent 生成的结果进行检查,确保其符合预期,特别是在有上一轮生成结果和用户反馈的场景下。
|
|
3
|
+
instructions:
|
|
4
|
+
url: ../prompts/check-structure-planning-result.md
|
|
5
|
+
input_schema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
structurePlan:
|
|
9
|
+
$ref: ./schema/structure-plan.yaml
|
|
10
|
+
description: 新一轮生成的结构规划。
|
|
11
|
+
originalStructurePlan:
|
|
12
|
+
$ref: ./schema/structure-plan.yaml
|
|
13
|
+
description: 上一轮生成的结构规划,用于对比。如果不存在,则检查默认通过。
|
|
14
|
+
structurePlanFeedback:
|
|
15
|
+
type: string
|
|
16
|
+
description: 用户针对上一轮结果提供的反馈。
|
|
17
|
+
required:
|
|
18
|
+
- structurePlan
|
|
19
|
+
output_schema:
|
|
20
|
+
type: object
|
|
21
|
+
properties:
|
|
22
|
+
isValid:
|
|
23
|
+
type: boolean
|
|
24
|
+
description: 检查是否通过。true 表示通过,false 表示不通过。
|
|
25
|
+
structureReviewFeedback:
|
|
26
|
+
type: string
|
|
27
|
+
description: 检查结果的详细说明。如果不通过,需要明确指出哪里不符合要求。
|
|
28
|
+
required:
|
|
29
|
+
- isValid
|
|
30
|
+
- structureReviewFeedback
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { AnthropicChatModel } from "@aigne/anthropic";
|
|
4
|
+
import { AIGNE } from "@aigne/core";
|
|
5
|
+
import { GeminiChatModel } from "@aigne/gemini";
|
|
6
|
+
import { OpenAIChatModel } from "@aigne/openai";
|
|
7
|
+
|
|
8
|
+
// Get current script directory
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
export default async function checkStructurePlanning(
|
|
12
|
+
{ originalStructurePlan, structurePlanFeedback, ...rest },
|
|
13
|
+
options
|
|
14
|
+
) {
|
|
15
|
+
// If originalStructurePlan exists, return directly
|
|
16
|
+
if (originalStructurePlan && !structurePlanFeedback) {
|
|
17
|
+
return {
|
|
18
|
+
structurePlan: originalStructurePlan,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const aigne = await AIGNE.load(join(__dirname, "../"), {
|
|
23
|
+
models: [
|
|
24
|
+
{
|
|
25
|
+
name: OpenAIChatModel.name,
|
|
26
|
+
create: (params) => new OpenAIChatModel({ ...params }),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: AnthropicChatModel.name,
|
|
30
|
+
create: (params) => new AnthropicChatModel({ ...params }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: GeminiChatModel.name,
|
|
34
|
+
create: (params) => new GeminiChatModel({ ...params }),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const panningAgent = aigne.agents["reflective-structure-planner"];
|
|
40
|
+
|
|
41
|
+
const result = await options.context.invoke(panningAgent, {
|
|
42
|
+
structurePlanFeedback,
|
|
43
|
+
originalStructurePlan,
|
|
44
|
+
...rest,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
...result,
|
|
49
|
+
structurePlanFeedback,
|
|
50
|
+
originalStructurePlan: originalStructurePlan
|
|
51
|
+
? originalStructurePlan
|
|
52
|
+
: JSON.parse(JSON.stringify(result.structurePlan || [])),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: content-detail-generator
|
|
2
|
+
description: 通用内容详情生成器,支持网站、文档、书籍、演示文稿等多种场景
|
|
3
|
+
instructions:
|
|
4
|
+
url: ../prompts/content-detail-generator.md
|
|
5
|
+
input_schema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
rules:
|
|
9
|
+
type: string
|
|
10
|
+
description: 用户的结构规划的要求
|
|
11
|
+
locale:
|
|
12
|
+
type: string
|
|
13
|
+
description: 用户语言,如 zh、en
|
|
14
|
+
datasources:
|
|
15
|
+
type: string
|
|
16
|
+
description: 结构规划的上下文,用于辅助结构规划
|
|
17
|
+
targetAudience:
|
|
18
|
+
type: string
|
|
19
|
+
description: 结构规划的目标受众
|
|
20
|
+
nodeName:
|
|
21
|
+
type: string
|
|
22
|
+
description: 结构规划的节点名称
|
|
23
|
+
originalStructurePlan: ./schema/structure-plan.yaml
|
|
24
|
+
title:
|
|
25
|
+
type: string
|
|
26
|
+
description:
|
|
27
|
+
type: string
|
|
28
|
+
path:
|
|
29
|
+
type: string
|
|
30
|
+
parentId:
|
|
31
|
+
type:
|
|
32
|
+
- string
|
|
33
|
+
- "null"
|
|
34
|
+
glossary:
|
|
35
|
+
type: string
|
|
36
|
+
description: 术语表
|
|
37
|
+
additionalInformation:
|
|
38
|
+
type: string
|
|
39
|
+
description: 额外补充信息
|
|
40
|
+
required:
|
|
41
|
+
- rules
|
|
42
|
+
- datasources
|
|
43
|
+
- originalStructurePlan
|
|
44
|
+
output_schema:
|
|
45
|
+
type: object
|
|
46
|
+
properties:
|
|
47
|
+
content:
|
|
48
|
+
type: string
|
|
49
|
+
required:
|
|
50
|
+
- content
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
type: team
|
|
2
|
+
name: detail-generator-and-translate
|
|
3
|
+
description: 生成文档详情并翻译
|
|
4
|
+
skills:
|
|
5
|
+
- ./transform-detail-datasources.mjs
|
|
6
|
+
- type: team
|
|
7
|
+
skills:
|
|
8
|
+
- ./content-detail-generator.yaml
|
|
9
|
+
- type: transform
|
|
10
|
+
jsonata: |
|
|
11
|
+
$merge([
|
|
12
|
+
$,
|
|
13
|
+
{ "reviewContent": content }
|
|
14
|
+
])
|
|
15
|
+
reflection:
|
|
16
|
+
reviewer: ./check-detail-result.mjs
|
|
17
|
+
is_approved: isApproved
|
|
18
|
+
max_iterations: 3
|
|
19
|
+
return_last_on_max_iterations: true
|
|
20
|
+
- ./batch-translate.yaml
|
|
21
|
+
- ./save-single-doc.mjs
|
|
22
|
+
input_schema:
|
|
23
|
+
type: object
|
|
24
|
+
properties:
|
|
25
|
+
nodeName:
|
|
26
|
+
type: string
|
|
27
|
+
description: 节点名称
|
|
28
|
+
default: Section
|
|
29
|
+
locale:
|
|
30
|
+
type: string
|
|
31
|
+
description: 语言
|
|
32
|
+
targetAudience:
|
|
33
|
+
type: string
|
|
34
|
+
description: 目标受众
|
|
35
|
+
glossary:
|
|
36
|
+
type: string
|
|
37
|
+
description: 术语表
|
|
38
|
+
currentPath:
|
|
39
|
+
type: string
|
|
40
|
+
description: 当前路径
|
|
41
|
+
feedback:
|
|
42
|
+
type: string
|
|
43
|
+
description: 反馈
|
|
44
|
+
sources:
|
|
45
|
+
type: array
|
|
46
|
+
items:
|
|
47
|
+
type: string
|
|
48
|
+
description: 源码路径
|
|
49
|
+
sourcesPath:
|
|
50
|
+
type: array
|
|
51
|
+
items:
|
|
52
|
+
type: string
|
|
53
|
+
description: 源码路径
|
|
54
|
+
default:
|
|
55
|
+
- ./
|
|
56
|
+
includePatterns:
|
|
57
|
+
type: array
|
|
58
|
+
items:
|
|
59
|
+
type: string
|
|
60
|
+
description: 包含的文件名
|
|
61
|
+
excludePatterns:
|
|
62
|
+
type: array
|
|
63
|
+
items:
|
|
64
|
+
type: string
|
|
65
|
+
description: 排除的文件名
|
|
66
|
+
outputDir:
|
|
67
|
+
type: string
|
|
68
|
+
description: 输出目录
|
|
69
|
+
default: ./aigne-docs/output
|
|
70
|
+
docsDir:
|
|
71
|
+
type: string
|
|
72
|
+
description: 文档目录
|
|
73
|
+
default: ./aigne-docs/docs
|
|
74
|
+
additionalInformation:
|
|
75
|
+
type: string
|
|
76
|
+
description: 额外补充信息
|
|
77
|
+
output_schema:
|
|
78
|
+
type: object
|
|
79
|
+
properties:
|
|
80
|
+
title:
|
|
81
|
+
type: string
|
|
82
|
+
description:
|
|
83
|
+
type: string
|
|
84
|
+
path:
|
|
85
|
+
type: string
|
|
86
|
+
content:
|
|
87
|
+
type: string
|
|
88
|
+
mode: sequential
|