← 全部示例
// 场景实例

合同审查 DAG(步骤级重试)

只提取一次合同条款,并行完成合规检查与摘要,最后生成 Markdown 通知。

适合评估可重复审查流程的法务运营与工作流团队

01 使用情境

合同审查有明确的先后关系,但并非每一步都必须排队执行。全部串行会浪费时间;某一步暂时失败后重跑整条流程,则会重复已经完成的工作。

一个合同审查场景

示例内置合同内容,以及一个四步骤的审查计划。

02 工作方式

一支分工明确、交接清楚的团队。

每个专职 Agent 只处理它需要的证据;互不依赖的工作同时进行,需要上游结果的工作则按顺序等待。

  1. 提取条款

    上游任务先准备两位审查者都需要的共同材料。

    条款提取员

    识别并整理相关合同条款。

  2. 从两个角度审查

    两项任务都在条款提取后开始,彼此不需要等待。

    并行执行
    合规检查员

    检查提取出的条款;失败时只重试这一步。

    合同摘要员

    生成简明的合同摘要。

  3. 完成交付

    最后一步等两个审查分支都完成后再开始。

    通知生成器

    把全部结果合并成最终 Markdown 通知。

03 最终结果

完整的审查通知

只有合规检查和合同摘要都成功后,系统才生成最终产物。

  1. 01提取后的条款上下文
  2. 02合规检查结果
  3. 03合同摘要与最终通知
  • 并行审查缩短流程中段。
  • 依赖关系让交接顺序清楚可见。
  • 步骤级重试只重复失败的工作,并使用指数退避。
范围与限制
这是编排与重试示例,不是法律意见或生产级合规政策。示例还提供强制失败命令,方便安全观察重试路径。
04 开发者实现

先运行,再查看具体实现。

上面是面向业务的解释;下面的命令、运行前提、API 和源码继续与真实仓库同步。

实现信息 OpenMultiAgent
场景实例 368 行

运行

在仓库的克隆里运行这个文件:

terminal
npx tsx packages/core/examples/cookbook/contract-review-dag.ts
FORCE_FAIL=task2 npx tsx packages/core/examples/cookbook/contract-review-dag.ts
前置条件
  • ANTHROPIC_API_KEY env var must be set.

OMA 与 provider 无关——这个示例按上面的 key 编写,但你也可以用 OpenAI、Gemini、Groq 等任意 provider 运行。 全部 provider →

展开完整同步源码 · 368 行

完整示例,从固定的 Framework commit 同步。

cookbook/contract-review-dag.ts
/**
* Contract Review DAG with Step-Level Retry
*
* Demonstrates DAG task orchestration with step-level retry using runTasks().
* Scenario: contract review pipeline with 4 tasks forming a DAG:
*
* [Task 1: extract-clauses]
* ├──→ [Task 2: compliance-check] ────┐
* │ ├──→ [Task 4: notify] → Markdown
* └──→ [Task 3: summary] ──────────────┘
*
* Key features:
* - DAG dependencies with parallel execution (Task 2 and 3 run concurrently)
* - Step-level retry on Task 2/3/4 with exponential backoff
* - FORCE_FAIL=task2 env var triggers Task 2 failure on first attempt
*
* Run:
* npx tsx packages/core/examples/cookbook/contract-review-dag.ts
* FORCE_FAIL=task2 npx tsx packages/core/examples/cookbook/contract-review-dag.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY env var must be set.
*/
 
import { OpenMultiAgent } from '../../src/index.js'
import type { AgentConfig, OrchestratorEvent } from '../../src/types.js'
import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
 
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
 
// ---------------------------------------------------------------------------
// Attempt counter for FORCE_FAIL mechanism (closure-based per agent)
// ---------------------------------------------------------------------------
const attemptCounter = new Map<string, number>()
 
function getAndIncrementAttempt(agentName: string): number {
const count = (attemptCounter.get(agentName) ?? 0) + 1
attemptCounter.set(agentName, count)
return count
}
 
// ---------------------------------------------------------------------------
// Step 1: Agent configurations (4 agents)
// ---------------------------------------------------------------------------
 
const extractorConfig: AgentConfig = {
name: 'extractor',
model: 'claude-sonnet-4-6',
provider: 'anthropic',
systemPrompt: `You are a contract clause extraction specialist. Extract all clauses from the provided contract text and output as a JSON array.
Each object must have:
- id: clause number (string)
- title: clause title
- content: full clause text
- riskLevel: "low" | "medium" | "high"
Output ONLY valid JSON, no other text.`,
maxTurns: 1,
temperature: 0.1,
}
 
const complianceCheckerConfig: AgentConfig = {
name: 'compliance-checker',
model: 'claude-sonnet-4-6',
provider: 'anthropic',
systemPrompt: `You are a contract compliance auditor. Review each clause for regulatory and operational compliance.
For each clause, output:
- clauseId: clause number
- isCompliant: true/false
- issues: array of issues found (empty array if none)
- riskCategory: "none" | "regulatory" | "operational" | "legal"
Output ONLY valid JSON array, no other text.`,
maxTurns: 1,
temperature: 0.1,
beforeRun: (context) => {
const attempt = getAndIncrementAttempt('compliance-checker')
 
// Only trigger FORCE_FAIL on first attempt (attempt=1)
if (attempt === 1 && process.env.FORCE_FAIL === 'task2') {
throw new Error('[FORCE_FAIL_TRIGGERED]')
}
return context
},
}
 
const summarizerConfig: AgentConfig = {
name: 'summarizer',
model: 'claude-sonnet-4-6',
provider: 'anthropic',
systemPrompt: `You are a contract summary specialist. Generate an executive summary from the extracted clause list.
Include:
- Contract overview (main areas covered)
- Key clause summary (3-5 most important points)
- Risk callouts (highlight high-risk clauses)
- Recommended next steps
Output in Markdown format.`,
maxTurns: 1,
temperature: 0.2,
}
 
const notifierConfig: AgentConfig = {
name: 'notifier',
model: 'claude-sonnet-4-6',
provider: 'anthropic',
systemPrompt: `You are a report writer. Combine the compliance check results and summary into a final contract review report.
Include:
- Executive Summary
- Compliance Results
- Risk Details
- Recommended Actions
Output in Markdown format.`,
maxTurns: 1,
temperature: 0.2,
}
 
// ---------------------------------------------------------------------------
// Step 2: Task configurations (4 tasks)
// ---------------------------------------------------------------------------
 
interface TaskConfig {
title: string
description: string
assignee?: string
dependsOn?: readonly string[]
maxRetries?: number
retryDelayMs?: number
retryBackoff?: number
}
 
// Task configs will be created after reading contract text
 
// ---------------------------------------------------------------------------
// Step 3: Progress tracking
// ---------------------------------------------------------------------------
 
interface TaskTiming {
startTime: number
endTime?: number
}
 
const taskTimings = new Map<string, TaskTiming>()
const taskStartTimes = new Map<string, number>()
 
function handleProgress(event: OrchestratorEvent): void {
const ts = new Date().toISOString()
 
switch (event.type) {
case 'task_start': {
const task = event.data as { id: string; title: string } | undefined
if (task) {
const now = Date.now()
taskTimings.set(task.id, { startTime: now })
taskStartTimes.set(task.title, now) // Use title as key for easier verification
console.log(`[${ts}] task_start: ${task.title}`)
}
break
}
 
case 'task_complete': {
const task = event.data as { id: string; title: string } | undefined
if (task) {
const timing = taskTimings.get(task.id)
if (timing) {
timing.endTime = Date.now()
const duration = timing.endTime - timing.startTime
console.log(`[${ts}] task_complete: ${task.title} (${duration}ms)`)
}
}
break
}
 
case 'task_retry':
console.log(`[${ts}] task_retry:`, JSON.stringify(event.data))
break
 
case 'task_skipped':
console.log(`[${ts}] task_skipped: ${event.task}`)
break
 
case 'agent_start':
if (event.agent) {
console.log(`[${ts}] agent_start: ${event.agent}`)
}
break
 
case 'agent_complete':
if (event.agent) {
console.log(`[${ts}] agent_complete: ${event.agent}`)
}
break
 
case 'message':
if (typeof event.data === 'string') {
console.log(`[${ts}] message: ${event.data}`)
}
break
 
case 'error':
console.log(`[${ts}] error:`, JSON.stringify(event.data))
break
 
default:
break
}
}
 
// ---------------------------------------------------------------------------
// Step 4: Parallelism verification
// ---------------------------------------------------------------------------
 
function verifyParallelism(): void {
const t2Start = taskStartTimes.get('compliance-check')
const t3Start = taskStartTimes.get('summary')
 
if (t2Start !== undefined && t3Start !== undefined) {
const diff = Math.abs(t2Start - t3Start)
console.log('\n=== Parallelism Check ===')
console.log(`Task 2 (compliance-check) start: ${t2Start}`)
console.log(`Task 3 (summary) start: ${t3Start}`)
console.log(`Time difference: ${diff}ms`)
 
if (diff >= 500) {
console.error(
`ASSERTION FAILED: Task 2 and Task 3 start times differ by ${diff}ms (>= 500ms). ` +
`Expected parallel execution after Task 1 completes.`,
)
process.exit(1)
}
console.log(`Parallel execution (< 500ms): YES`)
console.log('========================\n')
} else {
console.log('\n=== Parallelism Check: Unable to verify (missing timing data) ===\n')
}
}
 
// ---------------------------------------------------------------------------
// Step 5: Orchestrator and Team configuration
// ---------------------------------------------------------------------------
 
const orchestrator = new OpenMultiAgent({
defaultModel: 'claude-sonnet-4-6',
defaultProvider: 'anthropic',
onProgress: handleProgress,
})
 
const team = orchestrator.createTeam('contract-review-team', {
name: 'contract-review-team',
agents: [extractorConfig, complianceCheckerConfig, summarizerConfig, notifierConfig],
sharedMemory: true,
})
 
// ---------------------------------------------------------------------------
// Step 6: Read contract text
// ---------------------------------------------------------------------------
 
const contractText = readFileSync(
join(__dirname, '..', 'fixtures', 'sample-contract.txt'),
'utf-8',
)
 
// ---------------------------------------------------------------------------
// Step 6b: Create task configs with contract text
// ---------------------------------------------------------------------------
 
const taskConfigs: TaskConfig[] = [
{
title: 'extract-clauses',
description: `Extract all clauses from the following contract text into structured JSON.\n\n=== CONTRACT TEXT ===\n${contractText}\n=== END CONTRACT ===\n\nOutput only valid JSON array.`,
assignee: 'extractor',
},
{
title: 'compliance-check',
description: 'Check each clause for regulatory and operational compliance. Using the clause list from Task 1 above.',
assignee: 'compliance-checker',
dependsOn: ['extract-clauses'],
maxRetries: 2,
retryDelayMs: 500,
retryBackoff: 2,
},
{
title: 'summary',
description: 'Generate executive summary of the contract. Using the clause list from Task 1 above.',
assignee: 'summarizer',
dependsOn: ['extract-clauses'],
maxRetries: 2,
retryDelayMs: 500,
retryBackoff: 2,
},
{
title: 'notify',
description: 'Generate final markdown report with all analysis results',
assignee: 'notifier',
dependsOn: ['compliance-check', 'summary'],
maxRetries: 2,
retryDelayMs: 500,
retryBackoff: 2,
},
]
 
console.log('Contract Review DAG with Step-Level Retry')
console.log('='.repeat(60))
console.log(`\nContract: ${contractText.split('\n')[0]}`)
console.log(`Length: ${contractText.split(/\s+/).length} words\n`)
console.log(`FORCE_FAIL mode: ${process.env.FORCE_FAIL === 'task2' ? 'ENABLED (task2)' : 'disabled'}\n`)
 
// ---------------------------------------------------------------------------
// Step 7: Run the DAG
// ---------------------------------------------------------------------------
 
async function main(): Promise<void> {
try {
console.log('[Orchestration] Starting DAG execution...\n')
 
const result = await orchestrator.runTasks(team, taskConfigs)
 
// Verify parallelism
verifyParallelism()
 
console.log('\n' + '='.repeat(60))
console.log('FINAL RESULT')
console.log('='.repeat(60))
console.log(`Success: ${result.success}`)
console.log(`Total tasks: ${result.tasks?.length ?? taskConfigs.length}`)
console.log(`Total token usage: ${result.totalTokenUsage.input_tokens} input, ${result.totalTokenUsage.output_tokens} output`)
 
// Output each agent's result
console.log('\n' + '='.repeat(60))
console.log('AGENT RESULTS')
console.log('='.repeat(60))
for (const [agentName, agentResult] of result.agentResults) {
console.log(`\n--- ${agentName} ---`)
console.log(agentResult.success ? agentResult.output : `[FAILED] ${agentResult.output}`)
}
 
if (result.success) {
console.log('\n--- Final Report ---')
const notifierResult = result.agentResults.get('notifier')
if (notifierResult?.output) {
console.log(notifierResult.output)
}
} else {
console.log('\nWorkflow failed.')
for (const task of result.tasks ?? []) {
if (task.status === 'failed') {
console.log(` - ${task.title}: failed`)
}
}
}
 
console.log('\n' + '='.repeat(60))
console.log('Done.')
} catch (error) {
console.error('Fatal error:', error)
process.exit(1)
}
}
 
main()
在 GitHub 查看 / 编辑
// 企业服务

要把它用到生产环境?

open-multi-agent 采用 MIT 许可、可自行免费运行。当你需要在期限内交付、集成,或获得支持时,元定义科技(YuanASI)提供商业交付与支持。

// 直接联系

把 Open Multi-Agent 用进真实业务

联系框架作者本人,帮你梳理 AI 落地目标、让 AI 真正与业务结合

可提供的工程服务
S-01

AI Agent 定制开发

业务梳理、Agent 设计、Prompt 评估、生产部署、私有化与持续支持。

S-02

多智能体系统集成

多 Agent 架构编排、RAG、CRM / ERP / API 对接、性能与稳定性调优。

S-03

企业 AI 咨询

AI 场景评估、技术选型、POC、ROI 估算与落地路线规划。