알리바바 SpringAI의 ReactAgent 구현 및 활용

알리바바 SpringAI는 대형 언어 모델과 도구를 결합하여 문제 해결을 위한 자동화 시스템을 제공합니다. 이를 통해 지속적인 추론 및 도구 호출이 가능한 반복적 프로세스를 지원합니다.

1. 에이전트 소개

에이전트는 대형 언어 모델(Large Language Model, LLM)과 도구들을 연결해 문제를 해결하는 시스템입니다. 이 시스템은 태스크를 처리하기 위해 도구를 선택하고 호출하며, 문제가 해결될 때까지 반복적으로 작동합니다.

Spring AI Alibaba에서는 ReactAgent 기반의 생산성 에이전트를 제공합니다. ReactAgent는 ReAct(Reasoning + Acting) 패턴을 기반으로 하며, 주어진 작업을 단계별로 해결하는 방식을 사용합니다.

1.1 ReactAgent의 이론적 기초

ReAct는 인간처럼 문제를 해결하는 과정을 시뮬레이션합니다. 전통적인 AI는 한 번에 답을 추측하는 데 반해, ReAct는 "추론 후 행동"이라는 접근 방식을 따릅니다.

ReAct의 네 가지 단계
  • 추론 (Reasoning): 문제 상황을 분석하고 첫 단계를 결정합니다.
  • 행동 (Acting): 분석 결과에 따라 실제 작업을 수행합니다.
  • 관찰 (Observation): 실행된 작업의 결과를 확인합니다.
  • 반복 (Iteration): 관찰된 결과를 바탕으로 다시 추론을 시작합니다.

이 과정은 문제 해결이 완료될 때까지 계속됩니다.

1.2 ReactAgent의 원리

ReactAgent는 그래프(Graph) 엔진 위에서 동작하며, 노드와 엣지로 구성된 구조를 사용합니다.

  • 노드 (Nodes): 특정 작업을 처리하는 역할을 합니다.
  • 엣지 (Edges): 노드 간 연결을 정의하며, 다음 작업의 흐름을 제어합니다.

에이전트는 LLM과의 대화를 통해 지속적으로 작업을 수행하며, 도구 호출이나 반복 제한 등의 조건을 설정할 수 있습니다.

1.3 핵심 구성 요소

아래는 ReactAgent를 사용하기 위한 기본 의존성 설정입니다.

<dependencies>
    <!-- Spring AI Alibaba Agent Framework -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        <version>1.1.2.0</version>
    </dependency>

    <!-- DashScope ChatModel Support -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        <version>1.1.2.0</version>
    </dependency>
</dependencies>
모델 설정

모델은 다음과 같이 구성할 수 있습니다.

  1. application.yml 설정:
spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
  1. 코드를 통한 직접 설정:
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;

DashScopeApi dashScopeApi = DashScopeApi.builder()
    .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
    .build();

DashScopeChatModel chatModel = DashScopeChatModel.builder()
    .dashScopeApi(dashScopeApi)
    .build();

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .build();
  1. 옵션 설정:
DashScopeChatModel chatModel = DashScopeChatModel.builder()
    .dashScopeApi(dashScopeApi)
    .defaultOptions(DashScopeChatOptions.builder()
        .withModel(DashScopeChatModel.DEFAULT_MODEL_NAME)
        .withTemperature(0.7) // 무작위성을 조절
        .withMaxToken(2000) // 최대 출력 길이
        .withTopP(0.9) // 핵심 샘플링 파라미터
        .build())
    .build();
도구 설정

도구는 다양한 방식으로 등록할 수 있습니다.

  1. FunctionToolCallback을 사용한 도구 생성:
public class SearchTool {
    public String search(String query) {
        return "검색 결과: " + query;
    }
}

ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchTool())
    .description("검색 도구")
    .build();

ReactAgent agent = ReactAgent.builder()
    .name("search_agent")
    .model(chatModel)
    .tools(searchTool)
    .build();
  1. @Tool 애노테이션을 사용한 도구 등록:
@Component
public class WeatherTools {

    @Tool(description = "특정 도시의 날씨 정보를 가져옵니다.")
    public String getWeatherForLocation(String city) {
        return "항상 맑음: " + city;
    }
}

@Autowired
private WeatherTools weatherTools;

ReactAgent agent = ReactAgent.builder()
    .name("weather_agent")
    .model(chatModel)
    .tools(ToolCallbacks.from(weatherTools))
    .build();
  1. 도구 오류 처리:
@Component
public class CustomToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {

    @Override
    public String process(ToolExecutionException exception) {
        return "도구 실패: 오류 코드 1928391, " + exception.getMessage();
    }
}

ReactAgent agent = ReactAgent.builder()
    .name("weather_agent")
    .model(chatModel)
    .tools(ToolCallbacks.from(weatherTools))
    .toolExecutionExceptionProcessor(customToolExecutionExceptionProcessor)
    .build();
시스템 프롬프트

시스템 프롬프트는 다음과 같이 설정할 수 있습니다.

  1. 직접 설정:
String systemPrompt = """
    당신은 유머러스한 날씨 예보 전문가입니다.
    사용 가능한 도구:
    - getWeatherForLocation: 특정 도시의 날씨 정보를 가져옵니다.
    """;

ReactAgent agent = ReactAgent.builder()
    .name("weather_agent")
    .model(chatModel)
    .systemPrompt(systemPrompt)
    .build();
  1. 동적 프롬프트:
public class DynamicPromptInterceptor extends ModelInterceptor {
    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        String userRole = (String) request.getContext().getOrDefault("user_role", "default");
        String dynamicPrompt = switch (userRole) {
            case "expert" -> "전문가와 대화 중입니다. 기술 세부 사항을 포함하세요.";
            case "beginner" -> "초보자와 대화 중입니다. 개념을 쉽게 설명하세요.";
            default -> "친절하고 전문적인 어조를 유지하세요.";
        };

        SystemMessage enhancedSystemMessage = new SystemMessage(dynamicPrompt);

        ModelRequest modified = ModelRequest.builder(request)
            .systemMessage(enhancedSystemMessage)
            .build();
        return handler.call(modified);
    }
}

ReactAgent agent = ReactAgent.builder()
    .name("adaptive_agent")
    .model(chatModel)
    .interceptors(new DynamicPromptInterceptor())
    .build();

1.4 에이전트 호출

에이전트는 다양한 방법으로 호출할 수 있습니다.

  1. call 메서드:
AssistantMessage response = agent.call("안녕하세요, 당신은 누구신가요?");
System.out.println(response.getText());
  1. invoke 메서드:
Optional<OverAllState> result = agent.invoke("시를 써주세요.");

if (result.isPresent()) {
    OverAllState state = result.get();
    Optional<Object> messages = state.value("messages");
    List<Message> messageList = (List<Message>) messages.get();
    System.out.println("메시지 목록: " + messageList);
}

1.5 고급 기능

구조화된 출력

구조화된 출력을 위해 outputType 또는 outputSchema를 사용할 수 있습니다.

  1. outputType 설정:
@Data
public class StructOutput {
    private String title;
    private String content;
    private String style;
}

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .outputType(StructOutput.class)
    .build();
  1. outputSchema 설정:
BeanOutputConverter<StructOutput> converter = new BeanOutputConverter<>(StructOutput.class);
String schema = converter.getFormat();

ReactAgent agent = ReactAgent.builder()
    .name("analysis_agent")
    .model(chatModel)
    .outputSchema(schema)
    .build();
메모리

대화의 상태를 유지하기 위해 메모리를 사용할 수 있습니다.

ReactAgent agent = ReactAgent.builder()
    .name("chat_agent")
    .model(chatModel)
    .saver(new MemorySaver())
    .build();

RunnableConfig config = RunnableConfig.builder()
    .threadId("user_123")
    .build();

agent.call("제 이름은 김사람입니다.", config);
agent.call("제 이름은 무엇인가요?", config); // 출력: "당신의 이름은 김사람입니다."
후크(Hooks)

후크는 에이전트 실행 중 특정 지점에 사용자 정의 로직을 삽입하는 방법입니다.

  1. AgentHook 예시:
@HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
public class LoggingHook extends AgentHook {
    @Override
    public CompletableFuture<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {
        System.out.println("에이전트 실행 시작");
        return CompletableFuture.completedFuture(Map.of());
    }

    @Override
    public CompletableFuture<Map<String, Object>> afterAgent(OverAllState state, RunnableConfig config) {
        System.out.println("에이전트 실행 종료");
        return CompletableFuture.completedFuture(Map.of());
    }
}
  1. MessagesModelHook 예시:
@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {
    private static final int MAX_MESSAGES = 10;

    @Override
    public AgentCommand beforeModel(List<Message> messages, RunnableConfig config) {
        if (messages.size() > MAX_MESSAGES) {
            return new AgentCommand(messages.subList(messages.size() - MAX_MESSAGES, messages.size()), UpdatePolicy.REPLACE);
        }
        return new AgentCommand(messages);
    }
}
인터셉터(Interceptors)

인터셉터는 입력/출력 보호 및 도구 감시를 위해 사용됩니다.

  1. ModelInterceptor 예시:
public class GuardrailInterceptor extends ModelInterceptor {
    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        if (containsSensitiveContent(request.getMessages())) {
            return ModelResponse.of(AssistantMessage.builder().content("부적절한 내용이 감지되었습니다.").build());
        }
        return handler.call(request);
    }
}
  1. ToolInterceptor 예시:
public class ToolMonitoringInterceptor extends ToolInterceptor {
    @Override
    public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
        long startTime = System.currentTimeMillis();
        try {
            ToolCallResponse response = handler.call(request);
            logSuccess(request, System.currentTimeMillis() - startTime);
            return response;
        } catch (Exception e) {
            logError(request, e, System.currentTimeMillis() - startTime);
            return ToolCallResponse.error(request.getToolCall(), "도구 실행 중 문제가 발생했습니다.");
        }
    }
}
스트리밍 출력

스트리밍 방식으로 출력을 받을 수도 있습니다.

Flux<NodeOutput> stream = agent.stream("복잡한 작업");
stream.subscribe(
    output -> {
        if (output instanceof StreamingOutput streamingOutput) {
            if (streamingOutput.getOutputType() == OutputType.AGENT_MODEL_STREAMING) {
                System.out.print(streamingOutput.message().getText());
            } else if (streamingOutput.getOutputType() == OutputType.AGENT_MODEL_FINISHED) {
                System.out.println("\n모델 출력 완료");
            }
        }
    },
    error -> System.err.println("오류: " + error),
    () -> System.out.println("에이전트 실행 완료")
);

태그: SpringAI ReactAgent AlibabaCloud

5월 29일 06:03에 게시됨