본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 02. 23:13

에이전트 태스크를 Temporal 액티비티로 실행하기

요약

Temporal 워크플로 오케스트레이션 환경에서 AgentEnsemble 태스크를 프로세스 내부에서 직접 실행하는 방법을 설명합니다. HTTP 호출 없이 Java 호출만으로 에이전트 태스크를 Temporal 액티비티로 통합하여 지연 시간을 줄이고 안정성을 높이는 기술을 다룹니다.

핵심 포인트

  • AgentEnsemble을 Temporal 액티비티 내에서 인프로세스로 직접 호출 가능
  • TaskExecutor와 EnsembleExecutor 두 가지 실행 모드 제공
  • HeartbeatEnsembleListener를 통한 효율적인 하트비팅 관리
  • 네트워크 지연 및 운영 복잡성 감소

만약 프로덕션 환경에서 Temporal을 실행하고 있다면, 여러분은 이미 장기 실행 워크플로 오케스트레이션 (workflow orchestration)의 어려운 부분들, 즉 내구적 실행 (durable execution), 액티비티 재시도 (activity retries), 하트비팅 (heartbeating), 워크플로 히스토리 (workflow history), 그리고 서비스 간 조정 (cross-service coordination) 문제를 해결한 상태입니다. 이제 남은 질문은 에이전트 태스크 (agent tasks)가 해당 모델에 어떻게 부합하느냐 하는 것입니다.

가장 명확한 답변은 AgentEnsemble을 별도의 서비스로 실행하고 Temporal 액티비티 (activities)에서 HTTP를 통해 호출하는 것이지만, 이는 지연 시간 (latency), 네트워크 장애 모드 (network failure modes), 그리고 운영해야 할 또 다른 프로세스를 추가하게 됩니다. 덜 명확하지만 더 나은 답변은 두 시스템을 전혀 분리할 필요가 없다는 것입니다.

agentensemble-executor 모듈을 사용하면 어떤 Temporal 액티비티에서도 AgentEnsemble 태스크를 프로세스 내부에서 직접 (directly in-process) 호출할 수 있습니다. HTTP 서버도 필요 없고, 라이브러리 내부에 Temporal SDK 의존성도 없습니다. 그저 Java 호출만 있으면 됩니다.

두 가지 실행 모드

이 모듈은 서로 다른 세분도 (granularity)를 가진 두 가지 실행기 (executors)를 제공합니다:

클래스세분도사용 시점
TaskExecutor하나의 태스크 = 하나의 외부 액티비티태스크별 Temporal 재시도, 타임아웃, 하트비팅 적용 시
EnsembleExecutor하나의 앙상블 = 하나의 외부 액티비티더 단순한 파이프라인; AgentEnsemble이 단일 액티비티 내부에서 내부 오케스트레이션을 처리

TaskExecutor는 개별 AI 단계에 대해 Temporal이 재시도 및 타임아웃 의미론 (semantics)을 관리하기를 원할 때 권장되는 패턴입니다. EnsembleExecutor는 파이프라인이 짧고 내부 재시도가 중요하지 않을 때 더 간단합니다.

하트비팅 작동 방식

장기 실행 작업을 Temporal 액티비티 내에 임베딩할 때 흔히 우려되는 부분은 하트비팅 (heartbeating)입니다. 액티비티가 충분히 자주 하트비트를 보내지 않으면, Temporal은 이를 타임아웃으로 간주합니다.

HeartbeatEnsembleListenerEnsembleListener 라이프사이클 이벤트를 임의의 Consumer<Object>로 연결해 줍니다. Temporal의 하트비트 메서드를 컨슈머 (consumer)로 전달하는 코드는 단 한 줄이면 충분합니다:

return executor.execute(request, Activity.getExecutionContext()::heartbeat);

컨슈머(Consumer)는 task_started, task_completed, tool_call, 그리고 llm_iteration_started 이벤트 발생 시 동작하며, 이는 일반적인 에이전트 워크로드(Agent workloads)를 고려할 때 2분의 하트비트(Heartbeat) 창이 매우 넉넉할 정도로 빈번하게 발생합니다. 하트비트 페이로드(Payload)는 Temporal의 기본 Jackson DataConverter로 직렬화 가능한 HeartbeatDetail 레코드이므로, Temporal UI에서 확인할 수 있으며 Activity.getLastHeartbeatDetails()를 통해 접근할 수 있습니다.

완전한 Temporal 통합

권장되는 패턴은 각 AgentEnsemble 태스크를 별도의 @ActivityMethod로 래핑(Wrap)하는 것입니다:

@ActivityInterface
public interface ResearchPipelineActivity {
    @ActivityMethod TaskResult research(TaskRequest request);
...

워크플로우(Workflow)는 액티비티(Activity)를 순차적으로 실행하며 상위 단계의 출력을 컨텍스트 엔트리(Context entries)로 전달합니다:

public class ResearchWorkflowImpl implements ResearchWorkflow {

    private final ResearchPipelineActivity activity = 
...

Temporal은 순서 제어(Sequencing), 재시도(Retry), 타임아웃(Timeout)을 처리합니다. AgentEnsemble은 LLM 호출, 도구 실행(Tool execution), 그리고 ReAct 루프(ReAct loop)를 처리합니다. 각 관심사(Concern)는 그에 맞게 설계된 시스템 내에 머무릅니다.

에이전트 프롬프트(Agent prompt)를 구성할 때 컨텍스트 엔트리와 명시적 입력(Explicit inputs)이 병합됩니다. 만약 두 항목이 동일한 키를 공유한다면, inputs()context()보다 우선순위를 갖습니다.

LLM 호출 없는 테스트

두 실행기(Executor) 모두 LLM 호출 없이 주입할 수 있는 테스트 더블(Test doubles)인 FakeTaskExecutorFakeEnsembleExecutor를 제공합니다:

FakeTaskExecutor fake = FakeTaskExecutor.builder()
    .whenDescriptionContains("Research", "AI is advancing rapidly in 2026.")
    .whenDescriptionContains("Write", "Article: AI reshapes every industry.")
...

인터페이스가 아닌 구체적인 타입(Concrete type)으로 TaskExecutor를 받는 생성자를 사용한다는 것은, 오직 테스트만을 목적으로 별도의 인터페이스를 추가하지 않고도 하위 타입인 FakeTaskExecutor를 주입할 수 있음을 의미합니다.

Temporal의 TestWorkflowEnvironment와 결합하면, 전체 워크플로우를 빠르고 결정론적인(Deterministic) 테스트 내에서 실행할 수 있습니다:

@BeforeEach
void setUp() {
    testEnv = TestWorkflowEnvironment.newInstance();
...

요청 시점의 모델 선택 (Model Selection at Request Time)

모델과 도구는 워커(worker) 측에서 구성되며 워크플로 히스토리(workflow history)로 직렬화되지 않습니다. TaskRequest 내의 modelName은 요청 시점에 특정 모델을 선택합니다:

ModelProvider models = SimpleModelProvider.builder()
    .model("gpt-4o-mini", cheapModel)
    .model("gpt-4o", premiumModel)
...

이를 통해 워크플로 코드를 이식 가능하게(portable) 유지할 수 있습니다. 워크플로는 모델을 이름으로 참조하고, 워커가 이를 해석(resolve)합니다. 기본 모델 인스턴스를 변경하는 것(제공자 교체, 파라미터 조정, 버전 업데이트 등)은 워크플로 코드의 변경을 필요로 하지 않습니다.

Temporal 전용이 아님 (Not Temporal-Specific)

하트비트 컨슈머(heartbeat consumer)는 일반적인 Consumer<Object>입니다. agentensemble-executor 모듈은 Temporal SDK 의존성이 없습니다. 동일한 실행기(executor)를 다음과 같은 모든 외부 오케스트레이터(orchestrator)와 함께 사용할 수 있습니다:

  • AWS Step Functions -- 상태 머신 액티비티 폴러(state machine activity poller)에 하트비트 콜백을 전달
  • Kafka Streams -- Processor 내부에서 execute() 호출
  • Spring Batch -- Tasklet으로 래핑(wrap)
  • 일반 스레드 (Plain threads) -- 하트비팅을 사용하지 않으려면 null 전달

설계 트레이드오프 (The Design Tradeoff)

액티비티당 태스크(Task-per-activity) 방식은 더 높은 운영 가시성(operational visibility)을 제공합니다. 각 태스크는 Temporal UI에서 별도의 항목으로 나타나며, 고유한 재시도 히스토리(retry history)와 타임아웃(timeout)을 가집니다. 액티비티당 앙상블(Ensemble-per-activity) 방식은 작성하기 더 간단하지만, Temporal의 관점에서는 전체 파이프라인을 블랙박스(black box)로 취급합니다.

더 깊은 트레이드오프는 오케스트레이션 지능(orchestration intelligence)을 어디에 둘 것인가에 관한 것입니다. 만약 Temporal 워크플로가 이미 정교하다면(태스크 유형 간 라우팅, 결과에 따른 분기, 여러 단계 간의 컨텍스트 전달 등), 액티비티당 태스크 방식이 자연스럽게 적합합니다. 만약 AgentEnsemble의 페이즈 그룹화(phase grouping), DAG 병렬성(DAG parallelism), 또는 페이즈 리뷰 게이트(phase review gates)가 흥미로운 조정(coordination) 작업을 수행하고 있다면, 액티비티당 앙상블 방식이 해당 로직을 프레임워크 내부에 유지하며 Temporal은 외부 생명주기(lifecycle)만을 처리하게 합니다.

executor 모듈은 integration guide에 문서화되어 있습니다. 소스 코드는 GitHub에서 확인할 수 있습니다.

이 두 가지 모드(two-mode) 설계가 여러분의 Temporal 워크플로(workflows)에 깔끔하게 매핑되는지, 아니면 두 executor 중 어느 쪽에도 맞지 않는 통합 패턴(integration patterns)이 있는지 궁금합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0