알리바바 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>
모델 설정
모델은 다음과 같이 구성할 수 있습니다.
- application.yml 설정:
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
- 코드를 통한 직접 설정:
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();
- 옵션 설정:
DashScopeChatModel chatModel = DashScopeChatModel.builder()
.dashScopeApi(dashScopeApi)
.defaultOptions(DashScopeChatOptions.builder()
.withModel(DashScopeChatModel.DEFAULT_MODEL_NAME)
.withTemperature(0.7) // 무작위성을 조절
.withMaxToken(2000) // 최대 출력 길이
.withTopP(0.9) // 핵심 샘플링 파라미터
.build())
.build();
도구 설정
도구는 다양한 방식으로 등록할 수 있습니다.
- 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();
- @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();
- 도구 오류 처리:
@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();
시스템 프롬프트
시스템 프롬프트는 다음과 같이 설정할 수 있습니다.
- 직접 설정:
String systemPrompt = """
당신은 유머러스한 날씨 예보 전문가입니다.
사용 가능한 도구:
- getWeatherForLocation: 특정 도시의 날씨 정보를 가져옵니다.
""";
ReactAgent agent = ReactAgent.builder()
.name("weather_agent")
.model(chatModel)
.systemPrompt(systemPrompt)
.build();
- 동적 프롬프트:
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 에이전트 호출
에이전트는 다양한 방법으로 호출할 수 있습니다.
- call 메서드:
AssistantMessage response = agent.call("안녕하세요, 당신은 누구신가요?");
System.out.println(response.getText());
- 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를 사용할 수 있습니다.
- 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();
- 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)
후크는 에이전트 실행 중 특정 지점에 사용자 정의 로직을 삽입하는 방법입니다.
- 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());
}
}
- 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)
인터셉터는 입력/출력 보호 및 도구 감시를 위해 사용됩니다.
- 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);
}
}
- 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("에이전트 실행 완료")
);