루프 에이전트¶
LoopAgent
¶
LoopAgent
는 하위 에이전트를 루프(즉, 반복적으로)로 실행하는 워크플로 에이전트입니다. 지정된 반복 횟수 또는 종료 조건이 충족될 때까지 일련의 에이전트를 반복적으로 실행합니다.
LoopAgent
는 코드를 수정하는 것처럼 반복적인 작업이나 점진적인 개선이 필요한 워크플로에 사용됩니다.
예제¶
- 음식 이미지를 생성할 수 있는 에이전트를 만들고 싶지만, 때때로 특정 수의 항목(예: 바나나 5개)을 생성하려고 할 때 이미지에 다른 수의 항목(예: 바나나 7개 이미지)이 생성되는 경우가 있습니다.
이미지 생성
,음식 항목 수
라는 두 가지 도구가 있습니다. 지정된 수의 항목을 올바르게 생성하거나 특정 반복 횟수 후에 이미지를 계속 생성하고 싶기 때문에LoopAgent
를 사용하여 에이전트를 구축해야 합니다.
다른 워크플로 에이전트와 마찬가지로, LoopAgent
는 LLM으로 구동되지 않으므로 실행 방식이 결정론적입니다. 즉, 워크플로 에이전트는 실행(즉, 루프)에만 관련이 있으며 내부 로직에는 관련이 없습니다. 워크플로 에이전트의 도구나 하위 에이전트는 LLM을 활용할 수도 있고 그렇지 않을 수도 있습니다.
작동 방식¶
LoopAgent
의 Run Async
메서드가 호출되면 다음 작업을 수행합니다:
- 하위 에이전트 실행: 하위 에이전트 목록을 순서대로 반복합니다. 각 하위 에이전트에 대해 에이전트의
Run Async
메서드를 호출합니다. -
종료 확인:
중요하게도,
LoopAgent
자체는 언제 루프를 중지할지 본질적으로 결정하지 않습니다. 무한 루프를 방지하기 위해 종료 메커니즘을 반드시 구현해야 합니다. 일반적인 전략은 다음과 같습니다:- 최대 반복 횟수:
LoopAgent
에 최대 반복 횟수를 설정합니다. 루프는 해당 반복 횟수 후에 종료됩니다. - 하위 에이전트로부터의 에스컬레이션: 하나 이상의 하위 에이전트를 설계하여 조건을 평가합니다(예: "문서 품질이 충분히 좋은가?", "합의에 도달했는가?"). 조건이 충족되면 하위 에이전트는 종료를 신호할 수 있습니다(예: 사용자 정의 이벤트를 발생시키거나, 공유 컨텍스트에 플래그를 설정하거나, 특정 값을 반환).
- 최대 반복 횟수:
전체 예제: 반복적인 문서 개선¶
문서를 반복적으로 개선하려는 시나리오를 상상해 보세요:
- 작성자 에이전트: 주제에 대한 초안을 생성하거나 수정하는
LlmAgent
입니다. -
비평가 에이전트: 초안을 비평하고 개선할 부분을 식별하는
LlmAgent
입니다.
이 설정에서 LoopAgent
는 반복적인 프로세스를 관리합니다. CriticAgent
는 문서가 만족스러운 품질 수준에 도달했을 때 "STOP" 신호를 반환하도록 설계되어 추가 반복을 방지할 수 있습니다. 또는, max iterations
매개변수를 사용하여 프로세스를 고정된 횟수의 주기로 제한하거나, 외부 로직을 구현하여 중지 결정을 내릴 수 있습니다. 루프는 최대 5번 실행되어 반복적인 개선이 무한정 계속되지 않도록 합니다.
전체 코드
# Part of agent.py --> Follow https://google.github.io/adk-docs/get-started/quickstart/ to learn the setup
import asyncio
import os
from google.adk.agents import LoopAgent, LlmAgent, BaseAgent, SequentialAgent
from google.genai import types
from google.adk.runners import InMemoryRunner
from google.adk.agents.invocation_context import InvocationContext
from google.adk.tools.tool_context import ToolContext
from typing import AsyncGenerator, Optional
from google.adk.events import Event, EventActions
# --- Constants ---
APP_NAME = "doc_writing_app_v3" # New App Name
USER_ID = "dev_user_01"
SESSION_ID_BASE = "loop_exit_tool_session" # New Base Session ID
GEMINI_MODEL = "gemini-2.0-flash"
STATE_INITIAL_TOPIC = "initial_topic"
# --- State Keys ---
STATE_CURRENT_DOC = "current_document"
STATE_CRITICISM = "criticism"
# Define the exact phrase the Critic should use to signal completion
COMPLETION_PHRASE = "No major issues found."
# --- Tool Definition ---
def exit_loop(tool_context: ToolContext):
"""Call this function ONLY when the critique indicates no further changes are needed, signaling the iterative process should end."""
print(f" [Tool Call] exit_loop triggered by {tool_context.agent_name}")
tool_context.actions.escalate = True
# Return empty dict as tools should typically return JSON-serializable output
return {}
# --- Agent Definitions ---
# STEP 1: Initial Writer Agent (Runs ONCE at the beginning)
initial_writer_agent = LlmAgent(
name="InitialWriterAgent",
model=GEMINI_MODEL,
include_contents='none',
# MODIFIED Instruction: Ask for a slightly more developed start
instruction=f"""You are a Creative Writing Assistant tasked with starting a story.
Write the *first draft* of a short story (aim for 2-4 sentences).
Base the content *only* on the topic provided below. Try to introduce a specific element (like a character, a setting detail, or a starting action) to make it engaging.
Topic: {{initial_topic}}
Output *only* the story/document text. Do not add introductions or explanations.
""",
description="Writes the initial document draft based on the topic, aiming for some initial substance.",
output_key=STATE_CURRENT_DOC
)
# STEP 2a: Critic Agent (Inside the Refinement Loop)
critic_agent_in_loop = LlmAgent(
name="CriticAgent",
model=GEMINI_MODEL,
include_contents='none',
# MODIFIED Instruction: More nuanced completion criteria, look for clear improvement paths.
instruction=f"""You are a Constructive Critic AI reviewing a short document draft (typically 2-6 sentences). Your goal is balanced feedback.
**Document to Review:**
```
{{current_document}}
```
**Task:**
Review the document for clarity, engagement, and basic coherence according to the initial topic (if known).
IF you identify 1-2 *clear and actionable* ways the document could be improved to better capture the topic or enhance reader engagement (e.g., "Needs a stronger opening sentence", "Clarify the character's goal"):
Provide these specific suggestions concisely. Output *only* the critique text.
ELSE IF the document is coherent, addresses the topic adequately for its length, and has no glaring errors or obvious omissions:
Respond *exactly* with the phrase "{COMPLETION_PHRASE}" and nothing else. It doesn't need to be perfect, just functionally complete for this stage. Avoid suggesting purely subjective stylistic preferences if the core is sound.
Do not add explanations. Output only the critique OR the exact completion phrase.
""",
description="Reviews the current draft, providing critique if clear improvements are needed, otherwise signals completion.",
output_key=STATE_CRITICISM
)
# STEP 2b: Refiner/Exiter Agent (Inside the Refinement Loop)
refiner_agent_in_loop = LlmAgent(
name="RefinerAgent",
model=GEMINI_MODEL,
# Relies solely on state via placeholders
include_contents='none',
instruction=f"""You are a Creative Writing Assistant refining a document based on feedback OR exiting the process.
**Current Document:**
```
{{current_document}}
```
**Critique/Suggestions:**
{{criticism}}
**Task:**
Analyze the 'Critique/Suggestions'.
IF the critique is *exactly* "{COMPLETION_PHRASE}":
You MUST call the 'exit_loop' function. Do not output any text.
ELSE (the critique contains actionable feedback):
Carefully apply the suggestions to improve the 'Current Document'. Output *only* the refined document text.
Do not add explanations. Either output the refined document OR call the exit_loop function.
""",
description="Refines the document based on critique, or calls exit_loop if critique indicates completion.",
tools=[exit_loop], # Provide the exit_loop tool
output_key=STATE_CURRENT_DOC # Overwrites state['current_document'] with the refined version
)
# STEP 2: Refinement Loop Agent
refinement_loop = LoopAgent(
name="RefinementLoop",
# Agent order is crucial: Critique first, then Refine/Exit
sub_agents=[
critic_agent_in_loop,
refiner_agent_in_loop,
],
max_iterations=5 # Limit loops
)
# STEP 3: Overall Sequential Pipeline
# For ADK tools compatibility, the root agent must be named `root_agent`
root_agent = SequentialAgent(
name="IterativeWritingPipeline",
sub_agents=[
initial_writer_agent, # Run first to create initial doc
refinement_loop # Then run the critique/refine loop
],
description="Writes an initial document and then iteratively refines it with critique using an exit tool."
)
import static com.google.adk.agents.LlmAgent.IncludeContents.NONE;
import com.google.adk.agents.LlmAgent;
import com.google.adk.agents.LoopAgent;
import com.google.adk.agents.SequentialAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.Map;
public class LoopAgentExample {
// --- Constants ---
private static final String APP_NAME = "IterativeWritingPipeline";
private static final String USER_ID = "test_user_456";
private static final String MODEL_NAME = "gemini-2.0-flash";
// --- State Keys ---
private static final String STATE_CURRENT_DOC = "current_document";
private static final String STATE_CRITICISM = "criticism";
public static void main(String[] args) {
LoopAgentExample loopAgentExample = new LoopAgentExample();
loopAgentExample.runAgent("Write a document about a cat");
}
// --- Tool Definition ---
@Schema(
description =
"Call this function ONLY when the critique indicates no further changes are needed,"
+ " signaling the iterative process should end.")
public static Map<String, Object> exitLoop(@Schema(name = "toolContext") ToolContext toolContext) {
System.out.printf("[Tool Call] exitLoop triggered by %s \n", toolContext.agentName());
toolContext.actions().setEscalate(true);
// Return empty dict as tools should typically return JSON-serializable output
return Map.of();
}
// --- Agent Definitions ---
public void runAgent(String prompt) {
// STEP 1: Initial Writer Agent (Runs ONCE at the beginning)
LlmAgent initialWriterAgent =
LlmAgent.builder()
.model(MODEL_NAME)
.name("InitialWriterAgent")
.description(
"Writes the initial document draft based on the topic, aiming for some initial"
+ " substance.")
.instruction(
"""
You are a Creative Writing Assistant tasked with starting a story.
Write the *first draft* of a short story (aim for 2-4 sentences).
Base the content *only* on the topic provided below. Try to introduce a specific element (like a character, a setting detail, or a starting action) to make it engaging.
Output *only* the story/document text. Do not add introductions or explanations.
""")
.outputKey(STATE_CURRENT_DOC)
.includeContents(NONE)
.build();
// STEP 2a: Critic Agent (Inside the Refinement Loop)
LlmAgent criticAgentInLoop =
LlmAgent.builder()
.model(MODEL_NAME)
.name("CriticAgent")
.description(
"Reviews the current draft, providing critique if clear improvements are needed,"
+ " otherwise signals completion.")
.instruction(
"""
You are a Constructive Critic AI reviewing a short document draft (typically 2-6 sentences). Your goal is balanced feedback.
**Document to Review:**
```
{{current_document}}
```
**Task:**
Review the document for clarity, engagement, and basic coherence according to the initial topic (if known).
IF you identify 1-2 *clear and actionable* ways the document could be improved to better capture the topic or enhance reader engagement (e.g., "Needs a stronger opening sentence", "Clarify the character's goal"):
Provide these specific suggestions concisely. Output *only* the critique text.
ELSE IF the document is coherent, addresses the topic adequately for its length, and has no glaring errors or obvious omissions:
Respond *exactly* with the phrase "No major issues found." and nothing else. It doesn't need to be perfect, just functionally complete for this stage. Avoid suggesting purely subjective stylistic preferences if the core is sound.
Do not add explanations. Output only the critique OR the exact completion phrase.
""")
.outputKey(STATE_CRITICISM)
.includeContents(NONE)
.build();
// STEP 2b: Refiner/Exiter Agent (Inside the Refinement Loop)
LlmAgent refinerAgentInLoop =
LlmAgent.builder()
.model(MODEL_NAME)
.name("RefinerAgent")
.description(
"Refines the document based on critique, or calls exitLoop if critique indicates"
+ " completion.")
.instruction(
"""
You are a Creative Writing Assistant refining a document based on feedback OR exiting the process.
**Current Document:**
```
{{current_document}}
```
**Critique/Suggestions:**
{{criticism}}
**Task:**
Analyze the 'Critique/Suggestions'.
IF the critique is *exactly* "No major issues found.":
You MUST call the 'exitLoop' function. Do not output any text.
ELSE (the critique contains actionable feedback):
Carefully apply the suggestions to improve the 'Current Document'. Output *only* the refined document text.
Do not add explanations. Either output the refined document OR call the exitLoop function.
""")
.outputKey(STATE_CURRENT_DOC)
.includeContents(NONE)
.tools(FunctionTool.create(LoopAgentExample.class, "exitLoop"))
.build();
// STEP 2: Refinement Loop Agent
LoopAgent refinementLoop =
LoopAgent.builder()
.name("RefinementLoop")
.description("Repeatedly refines the document with critique and then exits.")
.subAgents(criticAgentInLoop, refinerAgentInLoop)
.maxIterations(5)
.build();
// STEP 3: Overall Sequential Pipeline
SequentialAgent iterativeWriterAgent =
SequentialAgent.builder()
.name(APP_NAME)
.description(
"Writes an initial document and then iteratively refines it with critique using an"
+ " exit tool.")
.subAgents(initialWriterAgent, refinementLoop)
.build();
// Create an InMemoryRunner
InMemoryRunner runner = new InMemoryRunner(iterativeWriterAgent, APP_NAME);
// InMemoryRunner automatically creates a session service. Create a session using the service
Session session = runner.sessionService().createSession(APP_NAME, USER_ID).blockingGet();
Content userMessage = Content.fromParts(Part.fromText(prompt));
// Run the agent
Flowable<Event> eventStream = runner.runAsync(USER_ID, session.id(), userMessage);
// Stream event response
eventStream.blockingForEach(
event -> {
if (event.finalResponse()) {
System.out.println(event.stringifyContent());
}
});
}
}