agency-orchestrator 0.1.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/LICENSE +190 -0
- package/README.md +268 -0
- package/README.zh-CN.md +225 -0
- package/dist/agents/loader.d.ts +11 -0
- package/dist/agents/loader.js +93 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +276 -0
- package/dist/connectors/claude.d.ts +6 -0
- package/dist/connectors/claude.js +38 -0
- package/dist/connectors/interface.d.ts +5 -0
- package/dist/connectors/interface.js +1 -0
- package/dist/connectors/ollama.d.ts +9 -0
- package/dist/connectors/ollama.js +36 -0
- package/dist/connectors/openai-compatible.d.ts +14 -0
- package/dist/connectors/openai-compatible.js +43 -0
- package/dist/core/dag.d.ts +17 -0
- package/dist/core/dag.js +90 -0
- package/dist/core/executor.d.ts +20 -0
- package/dist/core/executor.js +174 -0
- package/dist/core/parser.d.ts +6 -0
- package/dist/core/parser.js +118 -0
- package/dist/core/template.d.ts +12 -0
- package/dist/core/template.js +30 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +139 -0
- package/dist/output/reporter.d.ts +19 -0
- package/dist/output/reporter.js +96 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +2 -0
- package/package.json +66 -0
- package/workflows/content-pipeline.yaml +83 -0
- package/workflows/product-review.yaml +77 -0
- package/workflows/story-creation.yaml +102 -0
- package/workflows/test-deepseek.yaml +68 -0
- package/workflows/test-real.yaml +35 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加载 agency-agents 的 .md 文件,提取角色定义
|
|
3
|
+
*
|
|
4
|
+
* 文件格式:
|
|
5
|
+
* ---
|
|
6
|
+
* name: 角色名
|
|
7
|
+
* description: 描述
|
|
8
|
+
* emoji: 🔧
|
|
9
|
+
* ---
|
|
10
|
+
* # 角色标题
|
|
11
|
+
* ...system prompt 内容...
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
15
|
+
/**
|
|
16
|
+
* 加载指定角色的定义
|
|
17
|
+
* @param agentsDir agency-agents 的 agents 目录路径
|
|
18
|
+
* @param rolePath 角色路径,如 "engineering/engineering-sre"
|
|
19
|
+
*/
|
|
20
|
+
export function loadAgent(agentsDir, rolePath) {
|
|
21
|
+
const fullPath = resolve(agentsDir, `${rolePath}.md`);
|
|
22
|
+
if (!existsSync(fullPath)) {
|
|
23
|
+
throw new Error(`角色文件不存在: ${fullPath}\n请确认 agents_dir 和 role 路径正确`);
|
|
24
|
+
}
|
|
25
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
26
|
+
return parseAgentFile(content, rolePath);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 解析 agent .md 文件内容
|
|
30
|
+
*/
|
|
31
|
+
function parseAgentFile(content, rolePath) {
|
|
32
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
33
|
+
if (!frontmatterMatch) {
|
|
34
|
+
// 没有 frontmatter,整个文件当 system prompt
|
|
35
|
+
return {
|
|
36
|
+
name: rolePath,
|
|
37
|
+
description: '',
|
|
38
|
+
systemPrompt: content.trim(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const frontmatterRaw = frontmatterMatch[1];
|
|
42
|
+
const body = frontmatterMatch[2];
|
|
43
|
+
// 简单解析 frontmatter(不用 js-yaml 避免循环依赖,frontmatter 结构简单)
|
|
44
|
+
const meta = {};
|
|
45
|
+
for (const line of frontmatterRaw.split('\n')) {
|
|
46
|
+
const colonIdx = line.indexOf(':');
|
|
47
|
+
if (colonIdx > 0) {
|
|
48
|
+
const key = line.slice(0, colonIdx).trim();
|
|
49
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
50
|
+
meta[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
name: meta.name || rolePath,
|
|
55
|
+
description: meta.description || '',
|
|
56
|
+
emoji: meta.emoji,
|
|
57
|
+
tools: meta.tools,
|
|
58
|
+
systemPrompt: body.trim(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 列出所有可用角色
|
|
63
|
+
*/
|
|
64
|
+
export function listAgents(agentsDir) {
|
|
65
|
+
const dir = resolve(agentsDir);
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
throw new Error(`agents 目录不存在: ${dir}`);
|
|
68
|
+
}
|
|
69
|
+
const agents = [];
|
|
70
|
+
// 遍历子目录
|
|
71
|
+
for (const dept of readdirSync(dir, { withFileTypes: true })) {
|
|
72
|
+
if (!dept.isDirectory())
|
|
73
|
+
continue;
|
|
74
|
+
// 跳过非 agent 目录
|
|
75
|
+
if (dept.name.startsWith('.') || dept.name === 'node_modules' || dept.name === 'scripts' ||
|
|
76
|
+
dept.name === 'integrations' || dept.name === 'examples')
|
|
77
|
+
continue;
|
|
78
|
+
const deptDir = join(dir, dept.name);
|
|
79
|
+
for (const file of readdirSync(deptDir, { withFileTypes: true })) {
|
|
80
|
+
if (!file.isFile() || !file.name.endsWith('.md'))
|
|
81
|
+
continue;
|
|
82
|
+
const rolePath = `${dept.name}/${file.name.replace('.md', '')}`;
|
|
83
|
+
try {
|
|
84
|
+
const agent = loadAgent(agentsDir, rolePath);
|
|
85
|
+
agents.push(agent);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// 跳过无法解析的文件
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return agents;
|
|
93
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agency-orchestrator CLI
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* ao run workflow.yaml --input key=value --input file=@path.md
|
|
7
|
+
* ao validate workflow.yaml
|
|
8
|
+
* ao plan workflow.yaml
|
|
9
|
+
* ao roles --agents-dir ./agents
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import { parseWorkflow, validateWorkflow } from './core/parser.js';
|
|
15
|
+
import { buildDAG, formatDAG } from './core/dag.js';
|
|
16
|
+
import { listAgents } from './agents/loader.js';
|
|
17
|
+
import { run } from './index.js';
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const command = args[0];
|
|
20
|
+
async function main() {
|
|
21
|
+
if (!command || command === '--help' || command === '-h') {
|
|
22
|
+
printHelp();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
switch (command) {
|
|
26
|
+
case 'run':
|
|
27
|
+
await handleRun();
|
|
28
|
+
break;
|
|
29
|
+
case 'validate':
|
|
30
|
+
handleValidate();
|
|
31
|
+
break;
|
|
32
|
+
case 'plan':
|
|
33
|
+
handlePlan();
|
|
34
|
+
break;
|
|
35
|
+
case 'roles':
|
|
36
|
+
handleRoles();
|
|
37
|
+
break;
|
|
38
|
+
case 'init':
|
|
39
|
+
await handleInit();
|
|
40
|
+
break;
|
|
41
|
+
case '--version':
|
|
42
|
+
case '-v':
|
|
43
|
+
console.log(getVersion());
|
|
44
|
+
break;
|
|
45
|
+
default: {
|
|
46
|
+
// 容错:用户可能漏了空格,如 "planworkflows/x.yaml"
|
|
47
|
+
const knownCmds = ['run', 'validate', 'plan', 'roles'];
|
|
48
|
+
const match = knownCmds.find(c => command.startsWith(c) && command.length > c.length);
|
|
49
|
+
if (match) {
|
|
50
|
+
console.error(`看起来少了个空格?试试:\n ao ${match} ${command.slice(match.length)}\n`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.error(`未知命令: ${command}\n`);
|
|
54
|
+
printHelp();
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function handleRun() {
|
|
61
|
+
const filePath = args[1];
|
|
62
|
+
if (!filePath) {
|
|
63
|
+
console.error('用法: ao run <workflow.yaml> [--input key=value ...]');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const inputs = parseInputArgs();
|
|
67
|
+
const outputDir = getArgValue('--output') || '.ao-output';
|
|
68
|
+
const quiet = args.includes('--quiet') || args.includes('-q');
|
|
69
|
+
try {
|
|
70
|
+
const result = await run(resolve(filePath), inputs, { outputDir, quiet });
|
|
71
|
+
process.exit(result.success ? 0 : 1);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error(`\n错误: ${err instanceof Error ? err.message : err}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function handleValidate() {
|
|
79
|
+
const filePath = args[1];
|
|
80
|
+
if (!filePath) {
|
|
81
|
+
console.error('用法: ao validate <workflow.yaml>');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const workflow = parseWorkflow(resolve(filePath));
|
|
86
|
+
const errors = validateWorkflow(workflow);
|
|
87
|
+
if (errors.length === 0) {
|
|
88
|
+
console.log(` ${workflow.name} — 校验通过`);
|
|
89
|
+
console.log(` ${workflow.steps.length} 个步骤, ${(workflow.inputs || []).length} 个输入`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.error(` ${workflow.name} — 校验失败:\n`);
|
|
93
|
+
for (const err of errors) {
|
|
94
|
+
console.error(` - ${err}`);
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(`错误: ${err instanceof Error ? err.message : err}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function handlePlan() {
|
|
105
|
+
const filePath = args[1];
|
|
106
|
+
if (!filePath) {
|
|
107
|
+
console.error('用法: ao plan <workflow.yaml>');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const workflow = parseWorkflow(resolve(filePath));
|
|
112
|
+
const errors = validateWorkflow(workflow);
|
|
113
|
+
if (errors.length > 0) {
|
|
114
|
+
console.error(`校验失败:\n${errors.map(e => ` - ${e}`).join('\n')}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const dag = buildDAG(workflow);
|
|
118
|
+
console.log(`\n ${workflow.name}\n`);
|
|
119
|
+
console.log(formatDAG(dag));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.error(`错误: ${err instanceof Error ? err.message : err}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function handleInit() {
|
|
127
|
+
const targetDir = resolve('agency-agents-zh');
|
|
128
|
+
if (existsSync(targetDir)) {
|
|
129
|
+
console.log(` agency-agents-zh 已存在,跳过下载`);
|
|
130
|
+
// 尝试更新
|
|
131
|
+
try {
|
|
132
|
+
execSync('git pull', { cwd: targetDir, stdio: 'pipe' });
|
|
133
|
+
console.log(' 已更新到最新版本');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
console.log(' (更新失败,使用现有版本)');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log(' 正在下载 agency-agents-zh (186 个 AI 角色定义)...\n');
|
|
141
|
+
try {
|
|
142
|
+
execSync('git clone --depth 1 https://github.com/jnMetaCode/agency-agents-zh.git', { stdio: 'inherit' });
|
|
143
|
+
console.log('\n 下载完成!');
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
console.error('\n 下载失败,请手动克隆:');
|
|
147
|
+
console.error(' git clone https://github.com/jnMetaCode/agency-agents-zh.git');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 显示角色数量
|
|
152
|
+
const agents = listAgents(targetDir);
|
|
153
|
+
console.log(` 共 ${agents.length} 个角色可用\n`);
|
|
154
|
+
console.log(' 接下来你可以:');
|
|
155
|
+
console.log(' ao roles 查看所有角色');
|
|
156
|
+
console.log(' ao plan workflows/product-review.yaml 查看执行计划');
|
|
157
|
+
console.log(' ao run workflows/story-creation.yaml 运行工作流');
|
|
158
|
+
}
|
|
159
|
+
function handleRoles() {
|
|
160
|
+
const agentsDir = getArgValue('--agents-dir') || resolveAgentsDir();
|
|
161
|
+
try {
|
|
162
|
+
const agents = listAgents(resolve(agentsDir));
|
|
163
|
+
console.log(`\n 共 ${agents.length} 个角色 (${agentsDir}):\n`);
|
|
164
|
+
for (const agent of agents) {
|
|
165
|
+
const emoji = agent.emoji || ' ';
|
|
166
|
+
console.log(` ${emoji} ${agent.name} — ${agent.description || '(无描述)'}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error(`错误: ${err instanceof Error ? err.message : err}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** 解析 --input key=value 和 --input key=@file 参数 */
|
|
175
|
+
function parseInputArgs() {
|
|
176
|
+
const inputs = {};
|
|
177
|
+
for (let i = 2; i < args.length; i++) {
|
|
178
|
+
if (args[i] === '--input' || args[i] === '-i') {
|
|
179
|
+
const pair = args[++i];
|
|
180
|
+
if (!pair) {
|
|
181
|
+
console.error('--input 需要 key=value 参数');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const eqIdx = pair.indexOf('=');
|
|
185
|
+
if (eqIdx < 1) {
|
|
186
|
+
console.error(`无效的 input 格式: ${pair} (应为 key=value)`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const key = pair.slice(0, eqIdx);
|
|
190
|
+
let value = pair.slice(eqIdx + 1);
|
|
191
|
+
// @file 语法:从文件读取值
|
|
192
|
+
if (value.startsWith('@')) {
|
|
193
|
+
const filePath = resolve(value.slice(1));
|
|
194
|
+
try {
|
|
195
|
+
value = readFileSync(filePath, 'utf-8');
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
console.error(`无法读取文件: ${filePath}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
inputs[key] = value;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return inputs;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 自动查找 agents 目录,按优先级:
|
|
209
|
+
* 1. ./agency-agents-zh (ao init 下载的)
|
|
210
|
+
* 2. ../agency-agents-zh (同级目录)
|
|
211
|
+
* 3. ./agents (自定义)
|
|
212
|
+
*/
|
|
213
|
+
function resolveAgentsDir() {
|
|
214
|
+
const candidates = [
|
|
215
|
+
'./agency-agents-zh',
|
|
216
|
+
'../agency-agents-zh',
|
|
217
|
+
'./agents',
|
|
218
|
+
];
|
|
219
|
+
for (const dir of candidates) {
|
|
220
|
+
const full = resolve(dir);
|
|
221
|
+
if (existsSync(full))
|
|
222
|
+
return dir;
|
|
223
|
+
}
|
|
224
|
+
return './agency-agents-zh';
|
|
225
|
+
}
|
|
226
|
+
function getArgValue(flag) {
|
|
227
|
+
const idx = args.indexOf(flag);
|
|
228
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
229
|
+
return args[idx + 1];
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
function getVersion() {
|
|
234
|
+
try {
|
|
235
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
236
|
+
return pkg.version;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return '0.1.0';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function printHelp() {
|
|
243
|
+
console.log(`
|
|
244
|
+
agency-orchestrator — Multi-Agent Workflow Engine
|
|
245
|
+
基于 agency-agents-zh 的多智能体编排引擎
|
|
246
|
+
|
|
247
|
+
Quick Start:
|
|
248
|
+
ao init 下载 186 个 AI 角色定义
|
|
249
|
+
ao roles 查看所有可用角色
|
|
250
|
+
ao plan <workflow.yaml> 查看执行计划 (DAG)
|
|
251
|
+
ao run <workflow.yaml> [options] 执行工作流
|
|
252
|
+
|
|
253
|
+
Commands:
|
|
254
|
+
init 下载/更新 agency-agents-zh
|
|
255
|
+
run <workflow.yaml> 执行工作流
|
|
256
|
+
validate <workflow.yaml> 校验工作流定义
|
|
257
|
+
plan <workflow.yaml> 查看执行计划
|
|
258
|
+
roles [--agents-dir path] 列出可用角色
|
|
259
|
+
|
|
260
|
+
Options:
|
|
261
|
+
--input, -i key=value 传入输入变量
|
|
262
|
+
--input, -i key=@file 从文件读取变量值
|
|
263
|
+
--output dir 输出目录 (默认 .ao-output/)
|
|
264
|
+
--quiet, -q 静默模式
|
|
265
|
+
--version, -v 版本号
|
|
266
|
+
|
|
267
|
+
Examples:
|
|
268
|
+
ao init
|
|
269
|
+
ao run workflows/story-creation.yaml -i premise='一个时间旅行的故事' -i style='悬疑'
|
|
270
|
+
ao run workflows/product-review.yaml -i prd_content=@prd.md
|
|
271
|
+
ao plan workflows/content-pipeline.yaml
|
|
272
|
+
|
|
273
|
+
Agents: https://github.com/jnMetaCode/agency-agents-zh
|
|
274
|
+
`);
|
|
275
|
+
}
|
|
276
|
+
main();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LLMConnector, LLMResult, LLMConfig } from '../types.js';
|
|
2
|
+
export declare class ClaudeConnector implements LLMConnector {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(apiKey?: string);
|
|
5
|
+
chat(systemPrompt: string, userMessage: string, config: LLMConfig): Promise<LLMResult>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude API Connector
|
|
3
|
+
*/
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
export class ClaudeConnector {
|
|
6
|
+
client;
|
|
7
|
+
constructor(apiKey) {
|
|
8
|
+
this.client = new Anthropic({
|
|
9
|
+
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
|
|
10
|
+
});
|
|
11
|
+
if (!this.client.apiKey) {
|
|
12
|
+
throw new Error('缺少 ANTHROPIC_API_KEY\n' +
|
|
13
|
+
'请设置环境变量: export ANTHROPIC_API_KEY=your-key\n' +
|
|
14
|
+
'或在 workflow YAML 中配置');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async chat(systemPrompt, userMessage, config) {
|
|
18
|
+
const response = await this.client.messages.create({
|
|
19
|
+
model: config.model,
|
|
20
|
+
max_tokens: config.max_tokens || 4096,
|
|
21
|
+
system: systemPrompt,
|
|
22
|
+
messages: [
|
|
23
|
+
{ role: 'user', content: userMessage },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
const content = response.content
|
|
27
|
+
.filter(block => block.type === 'text')
|
|
28
|
+
.map(block => block.type === 'text' ? block.text : '')
|
|
29
|
+
.join('\n');
|
|
30
|
+
return {
|
|
31
|
+
content,
|
|
32
|
+
usage: {
|
|
33
|
+
input_tokens: response.usage.input_tokens,
|
|
34
|
+
output_tokens: response.usage.output_tokens,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Connector — 本地模型,不需要 API key
|
|
3
|
+
*/
|
|
4
|
+
import type { LLMConnector, LLMResult, LLMConfig } from '../types.js';
|
|
5
|
+
export declare class OllamaConnector implements LLMConnector {
|
|
6
|
+
private baseUrl;
|
|
7
|
+
constructor(baseUrl?: string);
|
|
8
|
+
chat(systemPrompt: string, userMessage: string, config: LLMConfig): Promise<LLMResult>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class OllamaConnector {
|
|
2
|
+
baseUrl;
|
|
3
|
+
constructor(baseUrl) {
|
|
4
|
+
const raw = baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
|
5
|
+
this.baseUrl = raw.replace(/\/+$/, '');
|
|
6
|
+
}
|
|
7
|
+
async chat(systemPrompt, userMessage, config) {
|
|
8
|
+
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
model: config.model,
|
|
13
|
+
messages: [
|
|
14
|
+
{ role: 'system', content: systemPrompt },
|
|
15
|
+
{ role: 'user', content: userMessage },
|
|
16
|
+
],
|
|
17
|
+
stream: false,
|
|
18
|
+
options: {
|
|
19
|
+
num_predict: config.max_tokens || 2048,
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
throw new Error(`Ollama error ${response.status}: ${text}`);
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return {
|
|
29
|
+
content: data.message.content,
|
|
30
|
+
usage: {
|
|
31
|
+
input_tokens: data.prompt_eval_count || 0,
|
|
32
|
+
output_tokens: data.eval_count || 0,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Compatible Connector
|
|
3
|
+
* 支持 DeepSeek、智谱、通义、Moonshot 等兼容 OpenAI 格式的 API
|
|
4
|
+
*/
|
|
5
|
+
import type { LLMConnector, LLMResult, LLMConfig } from '../types.js';
|
|
6
|
+
export declare class OpenAICompatibleConnector implements LLMConnector {
|
|
7
|
+
private apiKey;
|
|
8
|
+
private baseUrl;
|
|
9
|
+
constructor(options?: {
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
});
|
|
13
|
+
chat(systemPrompt: string, userMessage: string, config: LLMConfig): Promise<LLMResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class OpenAICompatibleConnector {
|
|
2
|
+
apiKey;
|
|
3
|
+
baseUrl;
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.apiKey = options.apiKey || process.env.OPENAI_API_KEY || '';
|
|
6
|
+
this.baseUrl = options.baseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
|
7
|
+
// 去掉末尾的 /
|
|
8
|
+
this.baseUrl = this.baseUrl.replace(/\/+$/, '');
|
|
9
|
+
if (!this.apiKey) {
|
|
10
|
+
throw new Error('缺少 API Key,请通过参数或环境变量传入');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async chat(systemPrompt, userMessage, config) {
|
|
14
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
model: config.model,
|
|
22
|
+
max_tokens: config.max_tokens || 4096,
|
|
23
|
+
messages: [
|
|
24
|
+
{ role: 'system', content: systemPrompt },
|
|
25
|
+
{ role: 'user', content: userMessage },
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
throw new Error(`API error ${response.status}: ${text}`);
|
|
32
|
+
}
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
const content = data.choices?.[0]?.message?.content || '';
|
|
35
|
+
return {
|
|
36
|
+
content,
|
|
37
|
+
usage: {
|
|
38
|
+
input_tokens: data.usage?.prompt_tokens || 0,
|
|
39
|
+
output_tokens: data.usage?.completion_tokens || 0,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG 构建和拓扑排序
|
|
3
|
+
*/
|
|
4
|
+
import type { WorkflowDefinition, DAGNode } from '../types.js';
|
|
5
|
+
export interface DAG {
|
|
6
|
+
nodes: Map<string, DAGNode>;
|
|
7
|
+
/** 拓扑排序后的执行层级,每层内的节点可并行 */
|
|
8
|
+
levels: string[][];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 从 WorkflowDefinition 构建 DAG
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildDAG(workflow: WorkflowDefinition): DAG;
|
|
14
|
+
/**
|
|
15
|
+
* 格式化 DAG 为可读文本(用于 `ao plan` 命令)
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatDAG(dag: DAG): string;
|
package/dist/core/dag.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从 WorkflowDefinition 构建 DAG
|
|
3
|
+
*/
|
|
4
|
+
export function buildDAG(workflow) {
|
|
5
|
+
const nodes = new Map();
|
|
6
|
+
// 创建所有节点
|
|
7
|
+
for (const step of workflow.steps) {
|
|
8
|
+
nodes.set(step.id, {
|
|
9
|
+
step,
|
|
10
|
+
dependencies: step.depends_on || [],
|
|
11
|
+
dependents: [],
|
|
12
|
+
status: 'pending',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
// 构建反向依赖(谁依赖我)
|
|
16
|
+
for (const [id, node] of nodes) {
|
|
17
|
+
for (const dep of node.dependencies) {
|
|
18
|
+
const depNode = nodes.get(dep);
|
|
19
|
+
if (!depNode) {
|
|
20
|
+
throw new Error(`step "${id}" 依赖不存在的 step: "${dep}"`);
|
|
21
|
+
}
|
|
22
|
+
depNode.dependents.push(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 拓扑排序 — 按层分组(同层可并行)
|
|
26
|
+
const levels = topologicalLevels(nodes);
|
|
27
|
+
return { nodes, levels };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 拓扑排序,返回执行层级
|
|
31
|
+
* 每个层级内的节点互不依赖,可并行执行
|
|
32
|
+
*
|
|
33
|
+
* 例如:
|
|
34
|
+
* A → B → D
|
|
35
|
+
* A → C → D
|
|
36
|
+
* 结果: [[A], [B, C], [D]]
|
|
37
|
+
*/
|
|
38
|
+
function topologicalLevels(nodes) {
|
|
39
|
+
const inDegree = new Map();
|
|
40
|
+
for (const [id, node] of nodes) {
|
|
41
|
+
inDegree.set(id, node.dependencies.length);
|
|
42
|
+
}
|
|
43
|
+
const levels = [];
|
|
44
|
+
const remaining = new Set(nodes.keys());
|
|
45
|
+
while (remaining.size > 0) {
|
|
46
|
+
// 找出当前入度为 0 的节点
|
|
47
|
+
const currentLevel = [];
|
|
48
|
+
for (const id of remaining) {
|
|
49
|
+
if (inDegree.get(id) === 0) {
|
|
50
|
+
currentLevel.push(id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (currentLevel.length === 0) {
|
|
54
|
+
throw new Error('工作流存在循环依赖,无法拓扑排序');
|
|
55
|
+
}
|
|
56
|
+
// 移除本层节点,更新入度
|
|
57
|
+
for (const id of currentLevel) {
|
|
58
|
+
remaining.delete(id);
|
|
59
|
+
const node = nodes.get(id);
|
|
60
|
+
for (const dep of node.dependents) {
|
|
61
|
+
inDegree.set(dep, inDegree.get(dep) - 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
levels.push(currentLevel);
|
|
65
|
+
}
|
|
66
|
+
return levels;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 格式化 DAG 为可读文本(用于 `ao plan` 命令)
|
|
70
|
+
*/
|
|
71
|
+
export function formatDAG(dag) {
|
|
72
|
+
const lines = ['执行计划:\n'];
|
|
73
|
+
for (let i = 0; i < dag.levels.length; i++) {
|
|
74
|
+
const level = dag.levels[i];
|
|
75
|
+
const parallel = level.length > 1;
|
|
76
|
+
for (let j = 0; j < level.length; j++) {
|
|
77
|
+
const node = dag.nodes.get(level[j]);
|
|
78
|
+
const step = node.step;
|
|
79
|
+
const prefix = parallel ? (j === 0 ? '┌' : j === level.length - 1 ? '└' : '├') : '→';
|
|
80
|
+
const tag = parallel ? ' (并行)' : '';
|
|
81
|
+
lines.push(` 第${i + 1}层 ${prefix} [${step.id}] ${step.role || step.type}${tag}`);
|
|
82
|
+
if (node.dependencies.length > 0) {
|
|
83
|
+
lines.push(` 依赖: ${node.dependencies.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (i < dag.levels.length - 1)
|
|
87
|
+
lines.push(' │');
|
|
88
|
+
}
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG 执行引擎 — 核心调度器
|
|
3
|
+
*/
|
|
4
|
+
import type { DAGNode, LLMConnector, LLMConfig, WorkflowResult } from '../types.js';
|
|
5
|
+
import type { DAG } from './dag.js';
|
|
6
|
+
export interface ExecutorOptions {
|
|
7
|
+
connector: LLMConnector;
|
|
8
|
+
agentsDir: string;
|
|
9
|
+
llmConfig: LLMConfig;
|
|
10
|
+
concurrency: number;
|
|
11
|
+
inputs: Map<string, string>;
|
|
12
|
+
/** 每步完成的回调 */
|
|
13
|
+
onStepComplete?: (node: DAGNode) => void;
|
|
14
|
+
onStepStart?: (node: DAGNode) => void;
|
|
15
|
+
/** 一批并行步骤开始前的回调 */
|
|
16
|
+
onBatchStart?: (nodes: DAGNode[]) => void;
|
|
17
|
+
/** 一批并行步骤全部完成后的回调(按顺序) */
|
|
18
|
+
onBatchComplete?: (nodes: DAGNode[]) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function executeDAG(dag: DAG, options: ExecutorOptions): Promise<WorkflowResult>;
|