워크플로 패턴

워크플로 패턴 (Workflow Patterns)

개요의 빌딩 블록을 다양한 패턴과 결합해서 에이전트에 구조와 신뢰성을 더하는 방법을 소개하는 문서예요.

출처: 문서

본문

개요의 빌딩 블록을 이 패턴들과 조합하면 에이전트에 구조와 신뢰성을 더할 수 있어요.

접근 방식 선택 (Choose Your Approach)

다음 핵심 요소를 고려하세요:

  • 유연성 vs 제어 (Flexibility vs Control) - LLM이 얼마나 자유롭게 행동해야 하는지 vs 그 동작을 얼마나 엄격하게 제한해야 하는지
  • 오류 허용도 (Error Tolerance) - 사용 사례에서 실수가 발생하면 어떤 결과가 초래되는지
  • 비용 고려 (Cost Considerations) - 시스템이 복잡할수록 일반적으로 LLM 호출이 많아지고 비용도 높아져요
  • 유지보수 (Maintenance) - 아키텍처가 단순할수록 디버깅과 수정이 쉬워져요

요구사항을 충족하는 가장 간단한 접근 방식부터 시작하세요. 다음과 같은 경우에만 복잡성을 추가하세요:

  1. 작업을 명확한 단계로 분해할 때
  2. 특정 기능을 위한 툴을 추가할 때
  3. 품질 관리를 위한 피드백 루프를 구현할 때
  4. 복잡한 워크플로를 위해 여러 에이전트를 도입할 때

이제 이런 패턴들이 실제로 어떻게 동작하는지 예제를 살펴볼게요.

패턴 예제 (Patterns with Examples)

이 패턴들은 Anthropic의 효과적인 에이전트 구축 가이드에서 가져온 것으로, 포괄적인 워크플로를 만들기 위해 결합할 수 있는 빌딩 블록 역할을 해요. 각 패턴은 작업 실행의 특정 측면을 다루며, 이를 잘 조합하면 복잡한 문제를 위한 신뢰성 있는 솔루션을 만들 수 있어요.

순차 처리 (Sequential Processing / Chains)

가장 단순한 워크플로 패턴으로, 미리 정의된 순서대로 단계를 실행해요. 각 단계의 출력이 다음 단계의 입력이 되어 명확한 작업 체인을 만들어요. 콘텐츠 생성 파이프라인이나 데이터 변환 프로세스처럼 순서가 잘 정의된 작업에 이 패턴을 사용하세요.

import { generateText, Output } from 'ai';
__PROVIDER_IMPORT__;
import { z } from 'zod';

async function generateMarketingCopy(input: string) {
  const model = __MODEL__;

  // First step: Generate marketing copy
  const { text: copy } = await generateText({
    model,
    prompt: `Write persuasive marketing copy for: ${input}. Focus on benefits and emotional appeal.`,
  });

  // Perform quality check on copy
  const { output: qualityMetrics } = await generateText({
    model,
    output: Output.object({
      schema: z.object({
        hasCallToAction: z.boolean(),
        emotionalAppeal: z.number().min(1).max(10),
        clarity: z.number().min(1).max(10),
      }),
    }),
    prompt: `Evaluate this marketing copy for:
    1. Presence of call to action (true/false)
    2. Emotional appeal (1-10)
    3. Clarity (1-10)

    Copy to evaluate: ${copy}`,
  });

  // If quality check fails, regenerate with more specific instructions
  if (
    !qualityMetrics.hasCallToAction ||
    qualityMetrics.emotionalAppeal < 7 ||
    qualityMetrics.clarity < 7
  ) {
    const { text: improvedCopy } = await generateText({
      model,
      prompt: `Rewrite this marketing copy with:
      ${!qualityMetrics.hasCallToAction ? '- A clear call to action' : ''}
      ${qualityMetrics.emotionalAppeal < 7 ? '- Stronger emotional appeal' : ''}
      ${qualityMetrics.clarity < 7 ? '- Improved clarity and directness' : ''}

      Original copy: ${copy}`,
    });
    return { copy: improvedCopy, qualityMetrics };
  }

  return { copy, qualityMetrics };
}

라우팅 (Routing)

이 패턴은 모델이 컨텍스트와 중간 결과를 기반으로 워크플로에서 어떤 경로를 택할지 결정하게 해 줘요. 모델이 지능적인 라우터 역할을 하며, 워크플로의 서로 다른 분기 사이에서 실행 흐름을 지시해요. 서로 다른 처리 접근 방식이 필요한 다양한 입력을 다룰 때 사용하세요. 아래 예제에서 첫 번째 LLM 호출의 결과가 두 번째 호출의 모델 크기와 시스템 프롬프트를 결정해요.

import { generateText, Output } from 'ai';
__PROVIDER_IMPORT__;
import { z } from 'zod';

async function handleCustomerQuery(query: string) {
  const model = __MODEL__;

  // First step: Classify the query type
  const { output: classification } = await generateText({
    model,
    output: Output.object({
      schema: z.object({
        reasoning: z.string(),
        type: z.enum(['general', 'refund', 'technical']),
        complexity: z.enum(['simple', 'complex']),
      }),
    }),
    prompt: `Classify this customer query:
    ${query}

    Determine:
    1. Query type (general, refund, or technical)
    2. Complexity (simple or complex)
    3. Brief reasoning for classification`,
  });

  // Route based on classification
  // Set model and system prompt based on query type and complexity
  const { text: response } = await generateText({
    model:
      classification.complexity === 'simple'
        ? 'openai/gpt-6-luna'
        : 'openai/gpt-6-astra',
    instructions: {
      general:
        'You are an expert customer service agent handling general inquiries.',
      refund:
        'You are a customer service agent specializing in refund requests. Follow company policy and collect necessary information.',
      technical:
        'You are a technical support specialist with deep product knowledge. Focus on clear step-by-step troubleshooting.',
    }[classification.type],
    prompt: query,
  });

  return { response, classification };
}

병렬 처리 (Parallel Processing)

작업을 동시에 실행되는 독립적인 하위 작업으로 분해해요. 이 패턴은 구조화된 워크플로의 이점을 유지하면서 병렬 실행으로 효율성을 높여요. 예를 들어 여러 문서를 분석하거나 단일 입력의 서로 다른 측면을 동시에 처리(코드 리뷰처럼)할 수 있어요.

import { generateText, Output } from 'ai';
__PROVIDER_IMPORT__;
import { z } from 'zod';

// Example: Parallel code review with multiple specialized reviewers
async function parallelCodeReview(code: string) {
  const model = __MODEL__;

  // Run parallel reviews
  const [securityReview, performanceReview, maintainabilityReview] =
    await Promise.all([
      generateText({
        model,
        instructions:
          'You are an expert in code security. Focus on identifying security vulnerabilities, injection risks, and authentication issues.',
        output: Output.object({
          schema: z.object({
            vulnerabilities: z.array(z.string()),
            riskLevel: z.enum(['low', 'medium', 'high']),
            suggestions: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),

      generateText({
        model,
        instructions:
          'You are an expert in code performance. Focus on identifying performance bottlenecks, memory leaks, and optimization opportunities.',
        output: Output.object({
          schema: z.object({
            issues: z.array(z.string()),
            impact: z.enum(['low', 'medium', 'high']),
            optimizations: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),

      generateText({
        model,
        instructions:
          'You are an expert in code quality. Focus on code structure, readability, and adherence to best practices.',
        output: Output.object({
          schema: z.object({
            concerns: z.array(z.string()),
            qualityScore: z.number().min(1).max(10),
            recommendations: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),
    ]);

  const reviews = [
    { ...securityReview.output, type: 'security' },
    { ...performanceReview.output, type: 'performance' },
    { ...maintainabilityReview.output, type: 'maintainability' },
  ];

  // Aggregate results using another model instance
  const { text: summary } = await generateText({
    model,
    instructions: 'You are a technical lead summarizing multiple code reviews.',
    prompt: `Synthesize these code review results into a concise summary with key actions:
    ${JSON.stringify(reviews, null, 2)}`,
  });

  return { reviews, summary };
}

오케스트레이터-워커 (Orchestrator-Worker)

주 모델(오케스트레이터)이 전문화된 워커들의 실행을 조율해요. 각 워커는 특정 하위 작업에 최적화되어 있고, 오케스트레이터는 전반적인 컨텍스트를 유지하며 일관된 결과를 보장해요. 이 패턴은 서로 다른 유형의 전문성이나 처리가 필요한 복잡한 작업에 탁월해요.

import { generateText, Output } from 'ai';
__PROVIDER_IMPORT__;
import { z } from 'zod';

async function implementFeature(featureRequest: string) {
  // Orchestrator: Plan the implementation
  const { output: implementationPlan } = await generateText({
    model: __MODEL__,
    output: Output.object({
      schema: z.object({
        files: z.array(
          z.object({
            purpose: z.string(),
            filePath: z.string(),
            changeType: z.enum(['create', 'modify', 'delete']),
          }),
        ),
        estimatedComplexity: z.enum(['low', 'medium', 'high']),
      }),
    }),
    instructions:
      'You are a senior software architect planning feature implementations.',
    prompt: `Analyze this feature request and create an implementation plan:
    ${featureRequest}`,
  });

  // Workers: Execute the planned changes
  const fileChanges = await Promise.all(
    implementationPlan.files.map(async file => {
      // Each worker is specialized for the type of change
      const workerSystemPrompt = {
        create:
          'You are an expert at implementing new files following best practices and project patterns.',
        modify:
          'You are an expert at modifying existing code while maintaining consistency and avoiding regressions.',
        delete:
          'You are an expert at safely removing code while ensuring no breaking changes.',
      }[file.changeType];

      const { output: change } = await generateText({
        model: __MODEL__,
        output: Output.object({
          schema: z.object({
            explanation: z.string(),
            code: z.string(),
          }),
        }),
        instructions: workerSystemPrompt,
        prompt: `Implement the changes for ${file.filePath} to support:
        ${file.purpose}

        Consider the overall feature context:
        ${featureRequest}`,
      });

      return {
        file,
        implementation: change,
      };
    }),
  );

  return {
    plan: implementationPlan,
    changes: fileChanges,
  };
}

평가자-최적화자 (Evaluator-Optimizer)

중간 결과를 평가하는 전용 평가 단계를 추가해 워크플로에 품질 관리를 더해요. 평가 결과에 따라 워크플로가 계속 진행되거나, 조정된 매개변수로 재시도하거나, 교정 조치를 취해요. 이를 통해 자기 개선과 오류 복구가 가능한 견고한 워크플로를 만들 수 있어요.

import { generateText, Output } from 'ai';
__PROVIDER_IMPORT__;
import { z } from 'zod';

async function translateWithFeedback(text: string, targetLanguage: string) {
  let currentTranslation = '';
  let iterations = 0;
  const MAX_ITERATIONS = 3;

  // Initial translation
  const { text: translation } = await generateText({
    model: __MODEL__,
    instructions: 'You are an expert literary translator.',
    prompt: `Translate this text to ${targetLanguage}, preserving tone and cultural nuances:
    ${text}`,
  });

  currentTranslation = translation;

  // Evaluation-optimization loop
  while (iterations < MAX_ITERATIONS) {
    // Evaluate current translation
    const { output: evaluation } = await generateText({
      model: __MODEL__,
      output: Output.object({
        schema: z.object({
          qualityScore: z.number().min(1).max(10),
          preservesTone: z.boolean(),
          preservesNuance: z.boolean(),
          culturallyAccurate: z.boolean(),
          specificIssues: z.array(z.string()),
          improvementSuggestions: z.array(z.string()),
        }),
      }),
      instructions: 'You are an expert in evaluating literary translations.',
      prompt: `Evaluate this translation:

      Original: ${text}
      Translation: ${currentTranslation}

      Consider:
      1. Overall quality
      2. Preservation of tone
      3. Preservation of nuance
      4. Cultural accuracy`,
    });

    // Check if quality meets threshold
    if (
      evaluation.qualityScore >= 8 &&
      evaluation.preservesTone &&
      evaluation.preservesNuance &&
      evaluation.culturallyAccurate
    ) {
      break;
    }

    // Generate improved translation based on feedback
    const { text: improvedTranslation } = await generateText({
      model: __MODEL__,
      instructions: 'You are an expert literary translator.',
      prompt: `Improve this translation based on the following feedback:
      ${evaluation.specificIssues.join('\n')}
      ${evaluation.improvementSuggestions.join('\n')}

      Original: ${text}
      Current Translation: ${currentTranslation}`,
    });

    currentTranslation = improvedTranslation;
    iterations++;
  }

  return {
    finalTranslation: currentTranslation,
    iterationsRequired: iterations,
  };
}

더 알아보기 (Learn more)